{"owner":"iOfficeAI","repo":"OfficeCLI","hasSkills":true,"totalSkillsCount":67,"totalTokensCount":145545,"categories":["anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["skills/officecli-data-dashboard/SKILL.md","skills/officecli-docx/SKILL.md","skills/officecli-academic-paper/SKILL.md","skills/morph-ppt-3d/SKILL.md","skills/morph-ppt/SKILL.md","skills/officecli-pitch-deck/SKILL.md","skills/officecli-xlsx/SKILL.md","skills/officecli-financial-model/SKILL.md","skills/officecli-pptx/SKILL.md","skills/officecli-word-form/SKILL.md","SKILL.md","skills/officecli/SKILL.md","plugins/plugin-protocol.md","skills/morph-ppt/reference/decision-rules.md","skills/morph-ppt/reference/pptx-design.md","skills/morph-ppt/reference/styles/INDEX.md","skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md","skills/morph-ppt/reference/styles/bw--mono-line/style.md","skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md","skills/morph-ppt/reference/styles/bw--swiss-system/style.md","skills/morph-ppt/reference/styles/dark--architectural-plan/style.md","skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md","skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md","skills/morph-ppt/reference/styles/dark--circle-digital/style.md","skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md","skills/morph-ppt/reference/styles/dark--cyber-future/style.md","skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md","skills/morph-ppt/reference/styles/dark--editorial-story/style.md","skills/morph-ppt/reference/styles/dark--investor-pitch/style.md","skills/morph-ppt/reference/styles/dark--liquid-flow/style.md","skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md","skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md","skills/morph-ppt/reference/styles/dark--neon-productivity/style.md","skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md","skills/morph-ppt/reference/styles/dark--premium-navy/style.md","skills/morph-ppt/reference/styles/dark--sage-grain/style.md","skills/morph-ppt/reference/styles/dark--space-odyssey/style.md","skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md","skills/morph-ppt/reference/styles/dark--velvet-rose/style.md","skills/morph-ppt/reference/styles/light--bold-type/style.md","skills/morph-ppt/reference/styles/light--firmwise-saas/style.md","skills/morph-ppt/reference/styles/light--fluid-gradient/style.md","skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md","skills/morph-ppt/reference/styles/light--isometric-clean/style.md","skills/morph-ppt/reference/styles/light--minimal-corporate/style.md","skills/morph-ppt/reference/styles/light--minimal-product/style.md","skills/morph-ppt/reference/styles/light--project-proposal/style.md","skills/morph-ppt/reference/styles/light--spring-launch/style.md","skills/morph-ppt/reference/styles/light--training-interactive/style.md","skills/morph-ppt/reference/styles/light--watercolor-wash/style.md","skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md","skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md","skills/morph-ppt/reference/styles/mixed--duotone-split/style.md","skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md","skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md","skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md","skills/morph-ppt/reference/styles/vivid--energy-neon/style.md","skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md","skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md","skills/morph-ppt/reference/styles/warm--bloom-academy/style.md","skills/morph-ppt/reference/styles/warm--brand-refresh/style.md","skills/morph-ppt/reference/styles/warm--coral-culture/style.md","skills/morph-ppt/reference/styles/warm--earth-organic/style.md","skills/morph-ppt/reference/styles/warm--monument-editorial/style.md","skills/morph-ppt/reference/styles/warm--playful-organic/style.md","skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md","skills/morph-ppt/reference/styles/warm--vital-bloom/style.md"],"skills":{"skills/officecli-data-dashboard/SKILL.md":"---\nname: officecli-data-dashboard\ndescription: \"Use this skill to build a multi-element Excel dashboard — Dashboard sheet on open, multiple formula-driven KPI cards, multiple charts, sparklines, and conditional formatting — from CSV or tabular input. Trigger on: 'dashboard', 'KPI dashboard', 'analytics dashboard', 'executive dashboard', 'metrics dashboard', 'CSV to dashboard', 'data visualization'. Output is a single .xlsx. Scene-layer on officecli-xlsx: inherits every xlsx hard rule. DO NOT invoke for: a single budget tracker / one-sheet CSV-with-formatting (use xlsx), a 3-statement / DCF / LBO financial model (use financial-model), a weekly report with at most 1 chart and fewer than 10 rows (use xlsx).\"\n---\n\n# Data Dashboard (scene-layer on officecli-xlsx)\n\nA dashboard is not \"a spreadsheet with charts\". It is a composition: **one Dashboard sheet the user lands on** with formula-driven KPI cards, cell-range-linked charts, sparklines, and semantic conditional formatting. Everything else (raw data, aggregations) is upstream infrastructure the user should never need to open. This skill teaches the composition pattern. Everything about the xlsx engine — cells, formulas, batch JSON, shell quoting, validate, HTML preview — comes from `officecli-xlsx` and is not re-taught here.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**When a prop name, enum value, or alias is uncertain, consult help before guessing.**\n\n```bash\nofficecli help xlsx                          # element list\nofficecli help xlsx chart                    # full schema for charts\nofficecli help xlsx sparkline                # sparklines\nofficecli help xlsx conditionalformatting    # all CF rule types\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help wins**. DeferredAddKeys (`combosplit`, `holesize`) work on `add` only — see Reference.\n\n## Mental Model & Inheritance\n\nThis skill **inherits every xlsx hard rule** from `officecli-xlsx` — shell quoting, zero formula errors, visual delivery floor, batch JSON shape (`{\"command\":\"set\"|\"add\",\"path\":...,\"props\":{...}}` — key is `command`, NOT `action`), batch JSON dotted-name rule, chart data-feed forms, batch+resident limits, `validate` discipline. Read officecli-xlsx first; honour those rules, do not re-teach them here.\n\n**Reverse handoff — do NOT use this skill when:**\n\n- The ask is a **single-sheet CSV-with-formatting tracker** (no Dashboard sheet, no KPI cards, ≤ 1 chart) → go back to `officecli-xlsx`.\n- The ask is a **3-statement / DCF / LBO financial model** with blue-inputs / black-formulas / cross-sheet drivers → use `officecli-financial-model`.\n- The ask is a **weekly status report** with one SUMIF summary and one chart over < 10 rows → `officecli-xlsx`.\n\nThis skill only accepts: \"a Dashboard sheet the user opens first, multiple KPI cards, multiple charts, some CF / sparklines\".\n\n## Shell & Execution Discipline\n\n→ see officecli-xlsx §Shell & Execution Discipline for the baseline (quoting, heredoc for `!`, incremental execution).\n\nTwo increments specific to dashboards:\n\n- **Long chart `add` commands exceed 180 chars.** Always split across lines with trailing `\\`; never pack a chart command onto a single line. The longer the command, the higher the chance a shell-escape bug hides inside it.\n- **Multi-instance counts use `query --json | jq length`, never `raw-get | grep -c`.** Example: `officecli query \"$FILE\" chart --json | jq '.data.results | length'` for \"how many charts do I have?\".\n\n## Core Principles\n\nFive non-negotiable principles. If any one is violated the output is not a dashboard, it is a spreadsheet that happens to have a chart.\n\n1. **Formula-driven KPIs.** Every KPI value on the Dashboard sheet is a formula — `SUM`, `AVERAGE`, `IFERROR((...-...)/...,0)`, whatever — referring to cells on the Data / Summary sheet. Never hardcode a computed number. When the underlying data changes tomorrow, KPIs update on open.\n\n2. **Cell-range references for charts.** Every chart series reads from a cell range: `series1.values=\"Sheet1!B2:B13\"`. Inline `data=\"Revenue:100,200,300\"` is for a 5-minute demo, not a delivered dashboard. The one exception: data requires an aggregation Excel cannot express (rare) — document the exception in a comment cell.\n\n3. **Dashboard-first architecture.** KPI label cells, KPI value cells, charts, sparklines all live on the **Dashboard** sheet — the single sheet a user lands on. Raw imports and `SUMIFS` rollups live on Data / Summary sheets, upstream of the Dashboard. The user should never need to switch tabs to find the answer.\n\n4. **Visible cells only for chart sources.** LibreOffice does not evaluate formulas in hidden columns or hidden sheets at render time. A chart whose `series1.values` points at a hidden-column `SUMIFS` renders blank. Pattern: aggregate into a **visible** Summary sheet, point charts at Summary cells, hide only helper columns that are not chart sources.\n\n5. **Data-size-aware complexity.** A 10-row dataset does not get 5 KPIs and 4 charts. A 200-row dataset does not get 1 KPI and 1 chart. Scale up the composition with the input (table in §Design Ideas). Overbuilding is as wrong as underbuilding.\n\n## Requirements\n\nAll `officecli-xlsx` requirements apply (→ see officecli-xlsx §Requirements for Outputs). Dashboards add these:\n\n- **Dashboard sheet is the active tab on open.** Confirm 0-based sheet index with `officecli query \"$FILE\" sheet` BEFORE filling `activeTab=\"N\"`. Never guess the index.\n- **`calc.fullCalcOnLoad=true`.** Set via `officecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true`. Do NOT `raw-set` `<calcPr>` — it produces duplicate elements that fail validate.\n- **Refresh downstream cachedValue after every upstream edit.** `fullCalcOnLoad=true` schedules runtime recalc only; it does NOT refresh build-time `cachedValue`. After `set B=100 → set E==B+D → fix B=150`, E is stale until you re-issue E's formula (or close/reopen). Stale cache ships \"Net Change = 0\" to the board.\n- **Every chart has a descriptive title and every series has a name.** `\"Series1\"` in a legend is unfinished work.\n- **Every KPI value cell has a formula.** Verifiable: `officecli query \"$FILE\" 'Dashboard!:has(formula)' --json | jq '.data.results | length'` should equal your planned KPI count.\n- **Header row fill on every data sheet.** Data sheet, Summary sheet, and any secondary data sheet need row 1 filled (e.g., `fill=1F3864 + font.color=FFFFFF + font.bold=true`).\n- **10+ rows on Data sheet → ≥ 1 CF rule on a numeric column.** A 20-row table with zero visual scanning aid is a quality miss.\n- **Dashboard value columns sized to the widest expected cachedValue — not a fixed 22.** Rule of thumb at 24pt bold + currency numFmt: `width ≈ ceil((visible_chars + 2) × 1.3)`. A KPI holding `¥1,958,414,250` (14 visible chars with currency + commas) needs `width ≥ 28`; a 4-digit KPI still needs `width ≥ 22` as the floor. Hardcoding `22` for a 10+ digit KPI is how `###` ships to the user.\n- **Sparkline row height ≥ 20.** A sparkline in a default 15pt row is a flat squiggle — set `/Dashboard/row[N] height=22` (or 24 when paired with a 24pt KPI value cell in the same row).\n- **Print deliverables set `_xlnm.Print_Area` scoped to Dashboard** + hide non-Dashboard sheets + add `<pageSetup fitToPage/>`. Without all three, the print pipeline emits every sheet and Dashboard lands on page 2+. See §Print-ready delivery for the exact commands.\n\n## Quick Start\n\nMinimal viable dashboard: 12-month revenue CSV → 4 KPIs + 1 line chart + activeTab + fullCalcOnLoad. Adapt the numbers, don't copy-paste blind. Broken into phases so a single failed phase is obvious.\n\n**Phase 1 — Data sheet: create, import, format.**\n\n```bash\nFILE=my_dashboard.xlsx\nofficecli create \"$FILE\"\nofficecli import \"$FILE\" /Sheet1 --file sales.csv --header\nofficecli set \"$FILE\" '/Sheet1/col[A]' --prop width=12\nofficecli set \"$FILE\" '/Sheet1/col[B]' --prop width=15\nofficecli set \"$FILE\" '/Sheet1/B2:B13' --prop numFmt='$#,##0'\nofficecli set \"$FILE\" '/Sheet1/A1:B1' --prop fill=1F3864 --prop font.color=FFFFFF --prop font.bold=true\n```\n\n**Phase 2 — Dashboard sheet + one KPI card.**\n\n```bash\nofficecli add \"$FILE\" / --type sheet --prop name=Dashboard\nofficecli set \"$FILE\" '/Dashboard/col[A]' --prop width=22\nofficecli set \"$FILE\" '/Dashboard/col[B]' --prop width=12\nofficecli set \"$FILE\" /Dashboard/A1 --prop value=\"Total Revenue\" --prop font.size=9 --prop font.color=666666 --prop bold=true\nofficecli set \"$FILE\" /Dashboard/A2 --prop 'formula==SUM(Sheet1!B2:B13)' --prop numFmt='$#,##0' --prop font.size=24 --prop bold=true --prop font.color=2E7D32\n```\n\n**Phase 3 — Sparkline + chart.**\n\n```bash\nofficecli add \"$FILE\" /Dashboard --type sparkline --prop cell=B2 --prop range='Sheet1!B2:B13' --prop type=line --prop color=4472C4 --prop highPoint=true --prop highMarkerColor=FF0000\nofficecli add \"$FILE\" /Dashboard --type chart \\\n  --prop chartType=line \\\n  --prop title=\"Revenue Trend\" \\\n  --prop series1.name=\"Revenue\" \\\n  --prop series1.values='Sheet1!B2:B13' \\\n  --prop series1.categories='Sheet1!A2:A13' \\\n  --prop preset=dashboard --prop axisNumFmt='$#,##0' \\\n  --prop x=0 --prop y=5 --prop width=10 --prop height=15\n```\n\n**Phase 4 — fullCalcOnLoad → activeTab (LAST) → close → validate.**\n\n```bash\nofficecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true\n\n# Resolve Dashboard's 0-based index from the actual sheet list — never hardcode.\nDASH_IDX=$(officecli query \"$FILE\" sheet --json \\\n  | jq '[.data.results[].path] | index(\"/Dashboard\")')\nofficecli raw-set \"$FILE\" /workbook --xpath \"//x:sheets\" --action insertbefore \\\n  --xml \"<bookViews xmlns=\\\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\\\"><workbookView activeTab=\\\"$DASH_IDX\\\" /></bookViews>\"\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified end-to-end on a 12-row revenue CSV: `validate` reports no errors, Dashboard opens first, `Dashboard/A2.cachedValue` resolves (2,075,000 for the test data), chart renders with values linked.\n\n## Design Ideas\n\nOptions, not templates. The user's data and audience drive the choices.\n\n### Layout patterns (pick one, stay consistent)\n\n**Pattern 1 — executive summary** (board packs): KPI strip A1:H4, charts stack from row 6.\n```\n┌ KPI1 │ KPI2 │ KPI3 │ KPI4 ┐  rows 1-4\n├──────┴──────┴──────┴──────┤\n│     Chart 1 (wide)        │  rows 6-18\n├───────────────┬───────────┤\n│   Chart 2     │  Chart 3  │  rows 20-32\n```\n\n**Pattern 2 — ops console** (live ops): KPIs down A:B, charts fill C:L.\n```\n│ KPI1 │                   │\n│ KPI2 │    Chart 1        │  rows 1-12\n│ KPI3 │                   │\n│ KPI4 ├───────────────────┤\n│ KPI5 │    Chart 2        │  rows 14-26\n```\n\n**Pattern 3 — scorecard** (≥ 6 KPIs, no dominant chart): grid of 2×3 cards (label / value / sparkline).\n```\n│ KPI1 │ KPI2 │ KPI3 │  rows 1-4\n│ KPI4 │ KPI5 │ KPI6 │  rows 5-8\n```\n\n### Complexity scaling by data size\n\n| Rows | KPIs | Charts | Sparklines | CF rules | Preset |\n|---|---|---|---|---|---|\n| < 10 | 1–2 | 1 | skip | 0–1 | `minimal` |\n| 10–50 | 2–3 | 2 | only if sequential time-series | 1–2 | `dashboard` |\n| 50–200 | 3–5 | 2–3 | only if sequential time-series | 2–3 | `dashboard` |\n| 200+ | 3–5 | 3 | only if sequential time-series | 3–4 | `dashboard` |\n\n### Chart type selection\n\n| Data pattern | Chart type | Notes |\n|---|---|---|\n| Trend over time, one series | `line` | Add `trendline=linear` to show direction on noisy series |\n| Trend over time, multiple components | `line` (multi-series) or `columnStacked` | Stacked when components sum to a meaningful total |\n| Comparison across categories in time order | `column` | Not `bar` — horizontal bars break left-to-right time reading |\n| Part-of-whole breakdown | `doughnut` | Prefer over `pie`: `chartType=pie` has a known LibreOffice blank-render regression |\n| Budget vs actual | `combo` with `combosplit=1` | First series as bars, rest as lines |\n| Correlation | `scatter` | X-axis via `categories` / `series1.categories` — `series1.xValues` is UNSUPPORTED |\n\n### Preset options\n\n`--prop preset=<name>` on every chart. Options: `minimal`, `dashboard`, `corporate`, `magazine`, `colorful`, `monochrome`, `dark`. Pick one and stay consistent across all charts on a single Dashboard — mixing presets reads as accidental.\n\n### Conditional formatting — semantic colors\n\nFour CF rule types; each uses `--type <shorthand>` at `add` time:\n\n| Intent | `--type` | Typical props |\n|---|---|---|\n| Magnitude bar (sales, spend) | `databar` | `sqref=B2:B13 color=4472C4` — explicit `min=0 max=<plausible>` recommended for predictable scaling, but omitting them is valid (defaults to data min/max) |\n| Heat map (rates, growth) | `colorscale` | `sqref=D2:D13 mincolor=FFCDD2 midcolor=FFFFFF maxcolor=C8E6C9` |\n| Status indicator | `iconset` | `sqref=E2:E13 iconset=3Arrows` — see help for the full enum |\n| Custom business rule | `formulacf` | `sqref=B2:B13 'formula=$B2>=100000' fill=C8E6C9 font.color=2E7D32` — `font.bold` works on CF too |\n\nSemantic colors to stay consistent within a dashboard:\n\n- good / positive: fill `C8E6C9`, font `2E7D32`\n- bad / negative: fill `FFCDD2`, font `C62828`\n- neutral: fill `F5F5F5`, font `666666`\n\n### KPI card anatomy\n\nA card is a label cell + a value cell. The label is small gray (font.size=9, font.color=666666, bold); the value is large bold (font.size=24, bold=true, numFmt, font.color signals tone). One row of light fill (e.g. `F0F4FF`) across the card area gives the \"card\" read without building merged-cell scaffolds. Value column width must be sized to the largest cachedValue — never narrower than 22, often 26–32 for 8+ digit currency (see Requirements).\n\n### Chart width budget by title length\n\nAt the `dashboard` preset's default title font, the chart plot-box width (in column units) must stay ahead of the title string, or the title clips mid-word. Rule of thumb: `chart.width ≥ ceil(title.length × 0.18)`. A 35-character title (\"Department: Year-End Headcount vs Attrition Rate\") needs `width ≥ 7`; be safer and use 10–12. If the anchor cannot be widened, shorten the title to ≤ 25 characters — clipped titles in a board-ready deliverable are indefensible.\n\n`officecli get chart[N]` exposes numeric `width` (e.g. `width=480pt`) alongside `anchor` (e.g. `\"A6:K21\"`) at `.data.results[0].format`. Either is usable for Gate 2 — derive column span from anchor letters (A→K = 10 cols) when you need a column-unit budget.\n\n### Print-ready delivery (board-pack / investor-send / one-pager)\n\nTriggers: ask contains \"print\" / \"一页\" / \"董事会\" / \"投资人\". Four artefacts on the Dashboard sheet; non-Dashboard sheets hidden so the print pipeline emits one page only.\n\n```bash\n# 1. Print_Area scoped to Dashboard (xlnm convention).\nofficecli add \"$FILE\" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Dashboard --prop 'refersTo=Dashboard!$A$1:$H$36'\n# 2. fit-to-page on Dashboard.\nofficecli raw-set \"$FILE\" /Dashboard --xpath \"//x:worksheet\" --action prepend --xml '<sheetPr xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><pageSetUpPr fitToPage=\"1\"/></sheetPr>'\n# 3. Landscape page setup.\nofficecli raw-set \"$FILE\" /Dashboard --xpath \"//x:sheetData\" --action insertafter --xml '<pageSetup orientation=\"landscape\" paperSize=\"9\" fitToWidth=\"1\" fitToHeight=\"1\"/>'\n# 4. Hide non-Dashboard sheets — Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.\nfor S in Sheet1 Summary; do\n  officecli raw-set \"$FILE\" /workbook --xpath \"//x:sheet[@name='$S']\" --action setattr --xml \"state=hidden\" || true\ndone\n```\n\nDelete any `Print_Area` set on Data / Summary sheets — conflicting scopes emit multi-page output.\n\n## QA (REQUIRED — Delivery Gate)\n\n**Assume there are problems. Your job is to find them.** A chart that was rendered does not mean a chart that was meaningful. \"validate pass\" is not delivery; \"the Dashboard sheet reads like someone who knows the business made it\" is delivery.\n\n### Minimum cycle before \"done\"\n\nInherit the xlsx baseline (`view issues`, formula error queries, `validate`, HTML preview scan): → see officecli-xlsx §QA minimum cycle.\n\nThen run the dashboard-specific Delivery Gates. Each gate uses **COUNT-then-if** pattern with a `.data.*` wrapper — never chain `&& echo OK || echo FAIL`.\n\n**Gate 1 — KPI formula coverage.** Every planned KPI cell must carry a formula. Adjust `-lt 2` to your plan (4 KPIs → `-lt 4`).\n\n```bash\nKPI_FORMULAS=$(officecli query \"$FILE\" 'Dashboard!:has(formula)' --json | jq '.data.results | length')\n[ \"$KPI_FORMULAS\" -lt 2 ] && { echo \"REJECT Gate 1: $KPI_FORMULAS formula cells on Dashboard\"; exit 1; }\n```\n\n**Gate 2 — Chart count matches plan, every chart has data + plausible title width.**\n\n```bash\nCHART_COUNT=$(officecli query \"$FILE\" chart --json | jq '.data.results | length')\n[ \"$CHART_COUNT\" -lt 1 ] && { echo \"REJECT Gate 2: zero charts\"; exit 1; }\ncol_num () { local c=$1 n=0; for ((k=0;k<${#c};k++)); do n=$((n*26+$(printf '%d' \"'${c:$k:1}\")-64)); done; echo \"$n\"; }\nfor i in $(seq 1 \"$CHART_COUNT\"); do\n  JSON=$(officecli get \"$FILE\" \"/Dashboard/chart[$i]\" --json)\n  SC=$(echo \"$JSON\" | jq -r '.data.results[0].format.seriesCount // 0')\n  TITLE=$(echo \"$JSON\" | jq -r '.data.results[0].format.title // \"\"')\n  ANCHOR=$(echo \"$JSON\" | jq -r '.data.results[0].format.anchor // \"\"')\n  [ \"$SC\" = \"0\" ] || [ -z \"$TITLE\" ] && { echo \"REJECT Gate 2: chart[$i] seriesCount=$SC title='$TITLE'\"; exit 1; }\n  [ -z \"$ANCHOR\" ] && continue\n  LCOL=$(echo \"${ANCHOR%%:*}\" | sed 's/[0-9]*$//'); RCOL=$(echo \"${ANCHOR##*:}\" | sed 's/[0-9]*$//')\n  SPAN=$(( $(col_num \"$RCOL\") - $(col_num \"$LCOL\") + 1 ))\n  MIN=$(( (${#TITLE} * 18 + 99) / 100 ))\n  [ \"$SPAN\" -lt \"$MIN\" ] && { echo \"REJECT Gate 2: chart[$i] title=${#TITLE} chars needs width ≥ $MIN, anchor spans $SPAN\"; exit 1; }\ndone\n```\n\nNarrower titles at preset `minimal` / `magazine` may clip earlier than the 0.18 factor — spot-check.\n\n**Gate 3 — Chart series names populated (no \"Series1\" in legend).**\n\n```bash\nfor i in $(seq 1 \"$CHART_COUNT\"); do\n  BAD=$(officecli get \"$FILE\" \"/Dashboard/chart[$i]\" --json | jq '[.data.results[0].children[]? | select(.type == \"series\") | select((.format.name // \"\") | test(\"^Series[0-9]+$\"; \"i\"))] | length')\n  [ \"$BAD\" -gt 0 ] && { echo \"REJECT Gate 3: chart[$i] has $BAD auto-named series\"; exit 1; }\ndone\n```\n\n**Gate 4 — CF rules on Data sheet (10+ rows).**\n\n```bash\nCF_COUNT=$(officecli query \"$FILE\" conditionalformatting --json | jq '.data.results | length')\n[ \"$CF_COUNT\" -lt 1 ] && { echo \"REJECT Gate 4: zero CF rules on 10+ row data sheet\"; exit 1; }\n```\n\nNote: `query conditionalformatting` is the canonical element name; `query cf` returns 0 (not an alias).\n\n**Gate 5 — activeTab and fullCalcOnLoad set.** Compare against real Dashboard index (Dashboard-at-index-0 is a true pass).\n\n```bash\nDASH_IDX=$(officecli query \"$FILE\" sheet --json | jq '[.data.results[].path] | index(\"/Dashboard\")')\nACTIVE=$(officecli get \"$FILE\" /workbook --json | jq '.data.results[0].format.activeTab // -1')\nFULLCALC=$(officecli get \"$FILE\" /workbook --json | jq -r '.data.results[0].format[\"calc.fullCalcOnLoad\"] // false')\n[ \"$ACTIVE\" != \"$DASH_IDX\" ] && { echo \"REJECT Gate 5: activeTab=$ACTIVE Dashboard=$DASH_IDX\"; exit 1; }\n[ \"$FULLCALC\" != \"true\" ] && { echo \"REJECT Gate 5: calc.fullCalcOnLoad=$FULLCALC — stale caches will ship\"; exit 1; }\n```\n\n**Gate 6 — Placeholder sweep.** No build-time tokens in rendered output.\n\n```bash\nLEAKS=$(officecli view \"$FILE\" text 2>/dev/null | grep -niE '\\{\\{|\\$fy\\$|<TODO>|xxxx|TBD' | wc -l | tr -d ' ')\n[ \"$LEAKS\" -gt 0 ] && { echo \"REJECT Gate 6: $LEAKS placeholder tokens\"; exit 1; }\n```\n\n**Gate 7 — Visual delivery floor (ported from xlsx).** Run `officecli view \"$FILE\" html` and Read the returned HTML path. Confirm:\n\n- No `###` in any Dashboard or Data cell (columns too narrow).\n- No truncated KPI labels, sheet tab names, or chart titles.\n- No placeholder tokens rendered as text (`$fy$24`, `{var}`, `<TODO>`, `xxxx`).\n- Pie / doughnut slices render with distinct fill colors (if collapsed in LibreOffice, verify in the user's target viewer before declaring broken — → see officecli-xlsx §Known Issues/Renderer caveats).\n- No empty chart anchors — every chart has a visible, plausible plot.\n- Dashboard sheet opens first (tab highlighted, active area scrolled to top).\n\nIf `view html` is blocked (renderer conflict, headless, port busy), Gate 7 is still **mandatory** — run ALL fallback checks:\n\n```bash\n# a) Token / ### sweep.\nofficecli view \"$FILE\" text 2>/dev/null | grep -nE '###|\\{\\{|<TODO>|\\$fy\\$|xxxx' && { echo \"REJECT Gate 7: tokens or ### present\"; exit 1; }\n# b) Per-KPI: cachedValue length × coef must fit col width. coef=0.55 fit-to-page, 0.85 otherwise.\nfor CELL in A2 C2 E2 G2; do\n  CV=$(officecli get \"$FILE\" \"/Dashboard/$CELL\" --json | jq -r '.data.results[0].format.cachedValue // .data.results[0].text // \"\"')\n  W=$(officecli get \"$FILE\" \"/Dashboard/col[${CELL%%[0-9]*}]\" --json | jq -r '.data.results[0].format.width // 0')\n  CAP=$(echo \"$W * 0.55\" | bc -l | awk '{print int($1)}')\n  [ \"${#CV}\" -gt \"$CAP\" ] && { echo \"REJECT Gate 7: $CELL '$CV' (${#CV} chars) > cap $CAP\"; exit 1; }\ndone\n# c) Rerun Gate 2 title × 0.18 ≤ anchor span.  d) Log which fallback was used and why.\n```\n\nGate 7 must **NEVER** be skipped — skipping ships `###` to the user.\n\nIf scene keywords include print / 一页 / board / 投资人 / 董事会, extend Gate 7 with a structural print-scope check:\n\n```bash\nif echo \"$USER_REQ\" | grep -qiE 'print|一页|投资人|董事会|board'; then\n  # Every non-Dashboard sheet must be hidden or veryHidden.\n  # query sheet --json carries the name in .preview (no .name/.state); visibility lives in\n  # each sheet's own get .format.hidden (absent => visible), so iterate + probe per sheet.\n  LEAKING=\"\"\n  while IFS=$'\\t' read -r SPATH SNAME; do\n    [ \"$SNAME\" = \"Dashboard\" ] && continue\n    HIDDEN=$(officecli get \"$FILE\" \"$SPATH\" --json | jq -r '.data.results[0].format.hidden // false')\n    [ \"$HIDDEN\" != \"true\" ] && LEAKING=\"$LEAKING $SNAME\"\n  done < <(officecli query \"$FILE\" 'sheet' --json | jq -r '.data.results[] | [.path, .preview] | @tsv')\n  [ -n \"$LEAKING\" ] && { echo \"REJECT Gate 7 print-scope: visible non-Dashboard sheet(s):$LEAKING — hide before delivery\"; exit 1; }\n  # Dashboard must carry an explicit Print_Area named range.\n  PA=$(officecli query \"$FILE\" 'namedrange[name=\"_xlnm.Print_Area\"]' --json | jq '.data.results | length')\n  [ \"$PA\" -ge 1 ] || { echo \"REJECT Gate 7 print-scope: no _xlnm.Print_Area set\"; exit 1; }\nfi\n```\n\nThe user opens the file in their target viewer (Office / WPS / Numbers) for the final print preview — the skill does not render export artefacts.\n\n**Gate 8 — Formula sanity (cachedValue real, not stale/error).** `fullCalcOnLoad=true` refreshes at runtime, NOT build-time cache — so every formula cell must carry a non-empty, non-zero, non-error `cachedValue` now.\n\n```bash\nfor CELL in A2 C2 E2 G2; do\n  JSON=$(officecli get \"$FILE\" \"/Dashboard/$CELL\" --json)\n  [ -z \"$(echo \"$JSON\" | jq -r '.data.results[0].format.formula // \"\"')\" ] && continue\n  CV=$(echo \"$JSON\" | jq -r '.data.results[0].format.cachedValue // \"\"')\n  case \"$CV\" in\n    \"\" | \"0\" | \"#DIV/0!\" | \"#REF!\" | \"#N/A\" | \"#VALUE!\" | \"#NAME?\" | \"null\")\n      echo \"REJECT Gate 8: $CELL cachedValue='$CV' — re-issue formula or close+reopen\"; exit 1 ;;\n  esac\ndone\n```\n\nIf a KPI is genuinely zero (e.g. \"terminations this quarter\" = 0), whitelist it in the loop and document — default assumption is \"zero is broken\".\n\nIf anything fails, fix at source, re-run the full cycle.\n\n### Honest limits\n\nScatter charts do not accept `series1.xValues` (UNSUPPORTED) — feed the x-axis via `categories` / `series1.categories`. LibreOffice chart color drift / pie-slice collapse / checkbox double-box are viewer artifacts — spot-check in Office / WPS / Numbers first.\n\n## Reference\n\n- **Shorthand `--type` at `add`:** `chart`, `sparkline`, `databar`, `colorscale`, `iconset`, `formulacf`. CF rules map to `help xlsx conditionalformatting`; path suffix `/Sheet/cf[N]`.\n- **Full schemas live in help:** `officecli help xlsx chart` / `sparkline` / `conditionalformatting`. This skill does not mirror them.\n- **DeferredAddKeys (add-only):** `combosplit`, `holesize`. See D-1. (`preset`, `trendline`, `referenceline`, `axisNumFmt` now work on `set` too — help shows `[add/set]`.)\n- **Build order:** charts + sparklines + CF + tabColors first → `calc.fullCalcOnLoad=true` via high-level `set` → `raw-set activeTab` **LAST** (after all sheets exist).\n\n## Known Issues & Pitfalls\n\n### Dashboard-specific\n\n| # | Issue | Mitigation |\n|---|---|---|\n| D-1 | `combosplit` is a DeferredAddKey — works on `add` only. (`preset`, `referenceline`, `trendline`, `axisNumFmt` now apply on `set` too — help shows `[add/set]`.) | Set `combosplit` at `add` time; cannot apply after the fact — remove + re-add. The other four can be applied or changed post-creation via `set`. |\n| D-2 | `referenceline` format is `value:color:label:dash` (color BEFORE label). `\"0:Break-Even:FF0000:dash\"` fails `Invalid color value`. | Order is value, color, label, dash. |\n| D-3 | Scatter charts do NOT accept `series1.xValues` (UNSUPPORTED). Feed the x-axis through `categories` / `series1.categories`. | `--prop series1.categories=\"Sheet1!A2:A13\"` (or `--prop categories=\"Sheet1!A2:A13\"`) |\n| D-4 | `formulacf` honors `font.bold` / `font.italic` (written to the dxf font and surfaced on readback), alongside `fill` and `font.color`. | Use any of `fill` / `font.color` / `font.bold` / `font.italic` to signal a CF rule. |\n| D-5 | Dashboard column widths default to 8.43 — KPI values at 24pt bold show `###` | Size by cachedValue bracket: 4–6 digits → 22–24; 7–9 digits (million) → 26–30; 10+ digits (亿 / billion) → 32–36; 百亿 / 10-digit + currency symbol + fit-to-page landscape → **40–44**. Formula `ceil((visible_chars+2)*1.3)` is a starting point; always verify via Gate 7 fallback b). Sparkline columns: 12. |\n| D-6 | `raw-set activeTab` must be the LAST mutation. Inserting before all sheets exist shifts indices. | Finish all sheets / charts / CF / sparklines / tabColors, then `raw-set`. |\n| D-7 | `calc.fullCalcOnLoad` via `raw-set` creates duplicate `<calcPr>` → validate fails | Use `officecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true`. |\n| D-8 | LibreOffice does not evaluate hidden-column formulas at render → charts referencing hidden cells render blank | Aggregate into a visible Summary sheet, chart reads from Summary. Hide only columns that are not chart sources. |\n| D-9 | `chartType=pie` blank-renders in LibreOffice | Use `doughnut` as the safe substitute for part-of-whole breakdowns. |\n| D-10 | `SUMIFS` / `AVERAGEIFS` with date criteria fails silently if the criterion is a string | Wrap with `DATE()` or `DATEVALUE()`: `=SUMIFS(B2:B13,A2:A13,DATE(2025,1,5))`. |\n| D-11 | Summary sheet percentage formulas display as raw decimals (0.098) without `numFmt` | Set `numFmt=\"0.0%\"` at the same `set` call as the formula. |\n| D-12 | `import --header` sets freeze + AutoFilter but does NOT set column widths. | Set widths on `col[]`. `numFmt` on a `col[]` path now applies a column-level style (`<col s=...>`, schema-valid, reads back as `numberformat`); it formats blank cells in the column. Cells with their own style still need a per-cell-range `numFmt`. |\n| D-13 | Sparkline `highpoint` is a bool (highlight on/off), not a color. `--prop highpoint=FF0000` errors `Invalid boolean value` | `--prop highPoint=true --prop highMarkerColor=FF0000`. Same pattern for lowPoint / firstPoint / lastPoint and their *MarkerColor. |\n| D-14 | Sparkline cross-sectional data is meaningless (a region or department has no ordering) | Skip sparklines unless rows are a sequential time-series (dates, months, quarters). |\n| D-15 | Empty chart `add` is rejected (`Chart requires a 'data' property`) at the CLI layer — legacy skills that relied on silent accept will fail here | Always provide `series1.values=` / `dataRange=` / inline `data=` at chart `add` time. Treat Gate 2 seriesCount check as a belt-and-braces verification. |\n| D-16 | `fullCalcOnLoad=true` guarantees a **runtime** recalc when the end user opens the file; it does NOT refresh the build-time `cachedValue` in XML. Build sequence `set B=100 → set E==B+D → fix B=150` leaves `E.cachedValue` stale (board sees \"Net Change = 0\"). | After all upstream edits are final, re-issue every downstream formula (`officecli set \"$FILE\" /Sheet/E2 --prop formula==B2+D2`) OR `close` + re-open the file. Gate 8 verifies. |\n| D-17 | The built-in calc engine does NOT evaluate `SUMPRODUCT` with array-predicate form `SUMPRODUCT((A2:A97=X)*C2:C97*D2:D97)` — cachedValue stays `0`/`null`, Gate 8 rejects. Runtime Excel / WPS compute fine, but board-delivered XLSX with stale cache still ships `0`. | Rewrite as helper column + `SUMIF`: `F2==C2*D2` on source sheet, then `=SUMIF(B:B, \"Region X\", F:F)`. Or pre-aggregate in Summary sheet and chart from there. |\n\n### Inherited (pointer only)\n\nCross-sheet `!` trap, chart `anchor` / series immutability after create → see officecli-xlsx §Known Issues.\n","skills/officecli-docx/SKILL.md":"---\nname: officecli-docx\ndescription: \"Use this skill any time a .docx file is involved -- as input, output, or both. This includes: creating Word documents, reports, letters, memos, or proposals; reading, parsing, or extracting text from any .docx file; editing, modifying, or updating existing documents; working with templates, tracked changes, comments, headers/footers, or tables of contents. Trigger whenever the user mentions 'Word doc', 'document', 'report', 'letter', 'memo', or references a .docx filename.\"\n---\n\n# OfficeCLI DOCX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good docx looks like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help docx                         # List all docx elements\nofficecli help docx <element>               # Full element schema (e.g. paragraph, field, numbering, watermark, toc)\nofficecli help docx <verb> <element>        # Verb-scoped (e.g. add field, set section)\nofficecli help docx <element> --json        # Machine-readable schema\n```\n\nHelp is pinned to the installed CLI version. When this skill and help disagree, **help is authoritative**.\n\n## Mental Model\n\nA `.docx` is a ZIP of XML parts (`document.xml`, `styles.xml`, `numbering.xml`, `header*.xml`, `footer*.xml`, `comments.xml`, …). Everything the user sees — headings, tables, page numbers, TOC, tracked changes — is XML inside that ZIP. `officecli` gives you a semantic-path API (`/body/p[1]/r[2]`) over it, so you almost never touch raw XML; when you must, use `raw-set` (see the XML appendix).\n\n## Shell & Execution Discipline\n\ndocx paths contain `[]`; some prop values contain `$`. Both are shell metacharacters. Escaping happens at three layers — keep them separate.\n\n1. **Shell.** ALWAYS quote element paths: `\"/body/p[1]\"`, not `/body/p[1]` (zsh/bash glob `[N]`). Single-quote any value containing `$`: `--prop text='$50M'` — at any length, the whole value inside one pair of single quotes. Unquoted `$50M` is stripped to `M`; mixing `'…$var…'` and `\"…$50…\"` on one long string is where the `$50` silently vanishes.\n2. **CLI (`text=`).** The two-char escapes `\\n`, `\\v` and `\\t` ARE interpreted in `--prop text=` (same rule as pptx): `\\n` starts a **new paragraph** (each new `<w:p>` inherits the source paragraph's style), `\\v` is a **soft line break** within one paragraph (`<w:br/>`), `\\t` a `<w:tab/>`. Double the backslash (`\\\\n`) for a literal backslash-n (rarely wanted). Row-level table shortcuts `c1…cN` are the exception: there `\\n` is a `<w:br/>` line break inside the cell (see recipe (e)).\n3. **JSON (batch).** Batch does NOT interpret the two-char escapes — a JSON `\"\\\\n\"` or `\"\\\\v\"` lands as literal text. Use JSON escapes for the real characters: `\"\\n\"` (a newline) starts a new paragraph, `\"\\u000b\"` is a soft line break.\n\nIf in doubt, `view text` after writing and compare character-for-character.\n\n**Incremental execution.** `officecli` mutates the file on every call. Run commands one at a time and check each exit code — a 50-command script that fails at command 3 cascades silently. After any structural op (new style, table, TOC, section break) run `get` on it before stacking more.\n\n**Open/save lifecycle:** `officecli open <file>` at the start, `officecli save <file>` at the end to flush to disk — `save` only writes and leaves the resident warm for follow-up edits; reach for `officecli close <file>` only to release the resident on a one-shot handoff. Both are always safe (never error or lose work). For many paragraphs of one style, use `batch` (one pass for the whole array). **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (python-docx, Word, a renderer, delivery).\n\n**`$FILE` convention.** All commands use `\"$FILE\"` — set it once (`FILE=\"your-doc.docx\"`). Never copy a literal `doc.docx` / `review.docx` into output — always substitute your actual target.\n\n## Requirements for Outputs\n\nDeliverable standards every document MUST meet — know these before reaching for a command.\n\n**Clear hierarchy.** Every non-trivial document has Title → Heading 1 → Heading 2 → body, not a wall of unstyled `Normal` paragraphs. If `view outline` shows one flat list, the hierarchy is missing.\n\n**Explicit heading sizes** (Word default style sizes drift between templates): **H1 ≥ 18pt** (20pt for long reports), H2 = 14pt bold, H3 = 12pt bold, body = 11–12pt, line spacing 1.15–1.5x. Prefer `style=Heading1` over inline sizes so a retheme touches the definition once — but set explicit sizes when you can't trust the template's styles.\n\n**One body font, one accent.** One readable body font (Calibri, Cambria, Georgia, Times New Roman); accent color for heading emphasis or table headers, not rainbow formatting.\n\n**Spacing through properties.** Use `spaceBefore` / `spaceAfter` on paragraphs. Rows of empty paragraphs break pagination and are flagged by `view issues`.\n\n**Typographic quality.** New content uses curly quotes (`'` `'` `\"` `\"`), not ASCII — Unicode directly or XML entities (`&#x2018;`/`&#x2019;`/`&#x201C;`/`&#x201D;`) inside `raw-set`. En-dash `–` for ranges (`2024–2026`), em-dash `—` for parenthetical breaks.\n\n**Headers, footers, page numbers on any document > 1 page.** Page numbers go through a live `PAGE` field (`--prop field=page`), never the literal text \"Page 1\" — the CLI injects `<w:fldChar>` for you (see Headers & Footers).\n\n**Preserve existing templates.** When editing a file that already has a look, match it — existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY document)\n\nBefore declaring done, run `officecli view \"$FILE\" html` and Read the returned HTML path to confirm ALL of these:\n\n- **No placeholder tokens rendered as data.** `$xxx$`, `{var}`, `{{name}}`, `<TODO>`, `lorem`, `xxxx` must never appear in a heading, body, cover, TOC, caption, header, or footer. A literal `{name}` meant for a human to fill belongs inside a visible instruction paragraph (\"Replace `{name}` before sending\"), never as finished content.\n- **No truncated titles or overflowing cells.** Widen the column or set `wrapText` rather than trimming content.\n- **TOC present when the document has 3+ headings** (`--type toc`).\n- **Cover page ≥ 60% filled, last page ≥ 40% filled.** Pad a thin cover with subtitle / author / date / scope / key highlights; pad a \"Thank you\" last page with conclusion / next steps / contact / legal.\n- **No `\\$`, `\\t`, `\\n` literals in document text.** If `view text` shows these, a shell-escape layer leaked — delete the paragraph and re-enter it.\n\nIf any fails, STOP and fix before declaring done.\n\n## Common Workflow\n\nSix steps. Every non-trivial build follows this shape.\n\n1. **Open the file.** `officecli open \"$FILE\"` (resident default). New file: `officecli create \"$FILE\"` first.\n2. **Orient.** Existing file: `officecli view \"$FILE\" outline` — heading tree, section count, whether a TOC / watermark / tracked changes already exist. Never edit blind.\n3. **Build incrementally.** Structural first, content next, formatting last: styles & numbering defs → sections / page setup → headings & body → tables / images / fields / TOC → headers / footers → comments. After each structural op, `get` it back before stacking on top.\n4. **Format to spec.** Explicit heading sizes, spacing, widths, alignment, tabs, list indents — formatting is part of the deliverable, not optional polish.\n5. **Save, then trust structure over cached text.** `officecli save \"$FILE\"` writes the XML. TOC / PAGE / NUMPAGES / SEQ / PAGEREF fields carry **cached values** that may be stale or empty until a human recalculates (F9 in Word). Confirm fields *exist* (`get --depth 3` finds `<w:fldChar>`) rather than trusting the visible text.\n6. **QA — assume there are problems.** You are done after one fix-and-verify cycle finds zero new issues, not when your last command exited 0. See QA.\n\n## Quick Start\n\nMinimal viable docx: a heading, a body paragraph, a subheading, and a footer with a live page-number field. Adapt, don't copy-paste — your file, your content.\n\n```bash\nFILE=\"review.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Q4 2026 Review\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Revenue grew 18% year-over-year, ahead of plan.\" --prop size=11pt --prop spaceAfter=8pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Key Drivers\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Enterprise renewals, upsell, and a new EMEA region.\" --prop size=11pt\nofficecli add \"$FILE\" / --type footer --prop type=default --prop size=9pt --prop text=\"Page \" --prop field=page\nofficecli set \"$FILE\" \"/footer[1]/p[1]\" --prop align=center\nofficecli save \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified: `validate` returns `no errors found`; `get /footer[1] --depth 3` shows the 5-run PAGE field chain (begin / instrText / separate / cached value / end).\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` tells you what's already there; jump into `view text` / `get` / `query` once you know where to look.\n\n```bash\nofficecli view \"$FILE\" outline            # heading tree, section count, table/image counts, watermark, tracked-changes presence — orient here first\nofficecli view \"$FILE\" html               # Read the returned HTML path: first visual check after a batch of edits (hierarchy, empty-para spacing, missing TOC)\nofficecli view \"$FILE\" text --start 1 --end 80   # text for content QA; paths shown as [/body/p[N]] so you can jump back with get\nofficecli view \"$FILE\" annotated          # values + style/font/size + warnings per run\nofficecli view \"$FILE\" stats              # paragraph counts, font usage, style distribution\nofficecli view \"$FILE\" issues             # empty paras, missing alt text, spacing anomalies\n```\n\n`officecli watch \"$FILE\"` keeps a live preview running for the human user to open at their discretion — agent self-check uses `view html`. Final visual verification is the user opening the `.docx` in Word / WPS / Pages.\n\n**Inspect one element.** XPath-style semantic paths (1-based). Always quote — shells glob `[N]`. Use `[last()]` (with parens) for the last element; `[last]` errors. Add `--json` for machine output.\n\n```bash\nofficecli get \"$FILE\" /                          # document root: metadata, page setup\nofficecli get \"$FILE\" \"/body/p[1]\"                # one paragraph\nofficecli get \"$FILE\" \"/body/p[1]/r[1]\"           # one run (character-level formatting)\nofficecli get \"$FILE\" \"/body/tbl[1]\" --depth 3    # table with rows and cells\nofficecli get \"$FILE\" \"/footer[1]\" --depth 3      # footer — check for fldChar\nofficecli get \"$FILE\" \"/styles/Heading1\"          # style definition\nofficecli get \"$FILE\" /numbering --depth 2        # numbering abstractNum + num bindings\n```\n\n**Query across the document.** CSS-like selectors, for systematic checks rather than hand-walking. Operators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists). Full reference: `officecli query --help`.\n\n```bash\nofficecli query \"$FILE\" 'paragraph[style=Heading1]'       # all H1s\nofficecli query \"$FILE\" 'p:contains(\"quarterly\")'         # text match\nofficecli query \"$FILE\" 'p:empty'                         # empty paragraphs (clutter)\nofficecli query \"$FILE\" 'image:no-alt'                    # accessibility gaps\nofficecli query \"$FILE\" 'paragraph[size>=24pt]'           # numeric comparison\nofficecli query \"$FILE\" 'field[fieldType!=page]'          # fields other than PAGE\n```\n\n`query --json` wraps results in `.data.results[]` — `jq '.data.results | length'` to count.\n\n**Large documents.** Navigate by heading with `view outline` and jump with `query`; don't dump the whole body into context.\n\n## Creating & Editing\n\nVerbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`, `raw-set` (last-resort XML). Ninety percent of a build is paragraphs, runs, tables, a couple of images, a TOC, and a footer.\n\n### Paragraphs, runs, styles\n\nA paragraph (`p`) is a block; a run (`r`) is a span of consistent character formatting inside it. Set paragraph-level props (style, alignment, spacing, indent) on the `p`; set font / size / color / bold on the `r`.\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1 --prop size=18pt --prop bold=true --prop spaceAfter=12pt\nofficecli set \"$FILE\" \"/body/p[1]/r[1]\" --prop color=1F4E79\n```\n\nUse `spaceBefore` / `spaceAfter` for vertical spacing — never chains of empty paragraphs. For left indent use `--prop indent=720` (twips), `firstLineIndent=360` for first line, `hangingIndent=720` for hanging; leading spaces fire `view issues`.\n\n### Tables\n\nTables are `/body/tbl[N]` with rows `tr[N]` and cells `tc[N]`. Add with row/column counts, then fill.\n\n```bash\nofficecli add \"$FILE\" /body --type table --prop rows=4 --prop cols=3 --prop width=100%\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop header=true --prop c1=Quarter --prop c2=\"Revenue\" --prop c3=\"Growth\"\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[1]/r[1]\" --prop bold=true\n```\n\nRow-level `set` supports `height`, `header`, and `c1 / c2 / … / cN` text shortcuts (`cN` generalises to any column count). Cell formatting (bold, fill, color) goes on the cell's paragraph / run — **not** row-level. For per-cell borders, set cell-level `border.*` on the `tc` (`--prop border.bottom=\"single;6;000000;0\"`), or paragraph-level `pbdr.*` on the inner paragraph.\n\n**Horizontal rule = a paragraph bottom border, never a 1-row table.** A table-as-divider renders as an empty min-height box (worst in headers/footers). Use `pbdr.bottom` (`STYLE;SIZE;COLOR`) on the paragraph instead:\n\n```bash\nofficecli set \"$FILE\" \"/body/p[3]\" --prop pbdr.bottom=\"single;6;2E75B6\"\n```\n\n### Lists (bullets, numbered, multi-level)\n\nFor single-level bullets/numbers, set `listStyle` on the paragraph (`listStyle` is a paragraph prop, NOT a run prop — common mistake):\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"First item\" --prop listStyle=bullet\n```\n\nFor multi-level (legal-style 1 / 1.1 / 1.1.1), add an `abstractNum`, then a `num`, then reference the `numId` per paragraph:\n\n```bash\nofficecli add \"$FILE\" /numbering --type abstractnum --prop format=decimal     # → abstractNum id=0\nofficecli add \"$FILE\" /numbering --type num --prop abstractNumId=0             # → num id=1\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Section one\" --prop numId=1 --prop ilvl=0\n```\n\nIDs are 0-based: the first `abstractNum` is id=0; the `num` references it via `abstractNumId=0` and is itself assigned id=1. A non-existent `abstractNumId` errors, so check ids after creating. Verify with `officecli query \"$FILE\" 'paragraph[numId>0]'`. See `help docx abstractnum` / `help docx num` for level and format options.\n\n### Tab stops (signature lines, leader rows)\n\nTab stops are a first-class `tab` child of the paragraph; `pos` accepts `6in`/`6cm`/twips, `val` ∈ `left`/`center`/`right`, `leader` ∈ `none`/`dot`/`hyphen`/`underscore`. See `help docx tab`.\n\n```bash\nofficecli add \"$FILE\" \"/body/p[1]\" --type tab --prop pos=6in --prop val=right --prop leader=dot\n```\n\n**Leader caveat.** `leader=dot` alone emits no dots — the leader renders only when a real `<w:tab/>` character sits in a run between the text and the tab stop. Put one there with `\\t` in the text: define the stop (`add tab --prop pos=6in --prop val=right --prop leader=dot`), then `--prop text=\"Chapter 1\\t12\"` — the `\\t` becomes the `<w:tab/>` and dots fill to the right-aligned page number. (Literal `text=\"Chapter 1 ......... 12\"` also ships, but a real tab stop aligns cleanly.)\n\n### Fields (PAGE / NUMPAGES / DATE / MERGEFIELD / REF)\n\nFields are live values computed at render time. `fieldType` picks the field; `name` supplies the target (merge name or `ref` bookmark); `format` / `instr` add switches.\n\n| Field | Use | Example |\n|---|---|---|\n| `page` | current page number | `--prop field=page` on footer, or `--prop fieldType=page` inline |\n| `numpages` | total pages | `--prop field=numpages` / `--prop fieldType=numpages` |\n| `date` | today | `--prop fieldType=date --prop format='yyyy-MM-dd'` |\n| `mergefield` | template merge token | `--prop fieldType=mergefield --prop name=CustomerName` |\n| `ref` | cross-reference to a bookmark | `--prop fieldType=ref --prop name=bookmarkName` |\n\nFull `fieldType` enum (30+ values incl. `pageref`, `seq`, `styleref`, `docproperty`, `createdate`, …) is in `help docx field`. **There is NO `fieldInstr` fieldType** — use the `instruction` prop for raw field instruction text when typed shortcuts fall short. Picture switches (`MERGEFIELD Amount \\# \"#,##0.00\"`, `DATE \\@ \"yyyy年MM月\"`) go via `--prop instruction='…'` (mergefield's `format` prop is ignored with a warning — use `instruction`).\n\n```bash\nofficecli add \"$FILE\" \"/body/p[3]\" --type field --prop fieldType=mergefield --prop name=customer_name\n# Renders «customer_name» — visible placeholder, replaced in Word at mail-merge time.\n```\n\n**MERGEFIELD templates: never render placeholder literals.** A `{{customer_name}}` or `$NAME$` shown as body text is a failed template the recipient sees — insert a real MERGEFIELD (above), or confine literal tokens to an obvious instruction paragraph. Confirm with `query 'field[fieldType=mergefield]'`.\n\n**SEQ / PAGEREF / TOC field values.** officecli doesn't store rendered field values at write time. Recompute by what each path needs:\n\n- **SEQ numbering** (`Figure 1/2/3`): `officecli set \"$FILE\" / --prop recalcFields=seq` counts SEQ fields in body document order and writes the cached values (`evaluated` flips true; switches/formats in `help docx document`). Heading-relative `\\s` and SEQ in headers/footers defer to Word.\n- **PAGE / PAGEREF / NUMPAGES / TOC page numbers** need pagination, which officecli has no engine for — `officecli set \"$FILE\" /settings --prop updateFields=true` defers them to Word on open.\n\nUse both on a multi-figure document. Academic papers: see the `officecli-academic-paper` skill.\n\n### Headers & Footers (page numbering)\n\nSingle-command pattern — the CLI injects `<w:fldChar>`, so you never compose the field by hand:\n\n```bash\n# Empty first-page footer — auto-enables differentFirstPage so the cover has no page number\nofficecli add \"$FILE\" / --type footer --prop type=first --prop text=\"\"\n# Default footer with live page number\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=9pt --prop text=\"Page \" --prop field=page\n```\n\nWhen both exist, the default footer is `/footer[2]`; alone it is `/footer[1]`. **Verify**: `get --depth 3` must show `fldChar` children, not just a run with literal `\"Page\"` (`view outline` prints \"Footer: Page\" for both live fields AND static text — don't rely on it). Do NOT `set --prop differentFirstPage=true` — that prop is unsupported (exit 2 with `WARNING: UNSUPPORTED`; the element itself is still created — check `success` in `--json`); adding a first-type footer flips the bit. For composite **Page X of Y**, see recipe (b).\n\n### Table of Contents\n\nFor any document with 3+ headings, first verify that the requested range has real sources. A TOC source must use a built-in Heading style or a custom paragraph style with `outlineLvl`; bold or large Normal text is not a source. If no eligible source exists, stop and fix the heading structure instead of inserting a TOC that renders \"Error! No table of contents entries found.\"\n\n```bash\n# Built-in Heading styles are TOC sources.\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Introduction\" --prop style=Heading1\n# Custom paragraph styles need an outline level (0 = Heading 1).\nofficecli add \"$FILE\" /styles --type style --prop id=ThesisH1 --prop type=paragraph --prop outlineLvl=0\n# Add the TOC only after its sources exist.\nofficecli add \"$FILE\" /body --type toc --prop levels=\"1-3\" --prop title=\"Table of Contents\" --prop hyperlinks=true --index 0\n```\n\nPage numbers need pagination; OfficeCLI cannot calculate TOC page numbers itself. Set `updateFields=true` so Word recomputes the TOC (and all fields) on open. Without a Word-compatible field engine, report the TOC as dynamic and uncomputed; do not claim ready page numbers or guess a static TOC.\n\n```bash\nofficecli set \"$FILE\" /settings --prop updateFields=true\n```\n\nAddress the TOC directly with `/toc[1]` or `/tableofcontents` for `get`/`set`/`remove`.\n\n### Images\n\nPictures go inside a run. Alt text is mandatory for accessibility — pass `alt` directly at create time:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[5]\" --type picture --prop src=logo.png --prop width=1.5in --prop alt=\"Acme logo\"\n```\n\nConfirm `officecli query \"$FILE\" 'image:no-alt'` is empty before delivery.\n\n### Charts\n\nFor data, add a **native chart** — editable, themeable, accessible, re-renders in Word — never a flat PNG screenshot of a chart. `data=\"Label:v1,v2,…\"` per series; one `data=` per series (or `series1=`/`series2=`).\n\n```bash\nofficecli add \"$FILE\" /body --type chart --prop chartType=bar --prop title=\"Revenue by Region\" --prop categories=\"EMEA,APAC,Americas\" --prop data=\"2026:120,150,180\"\n```\n\n`chartType` ∈ bar / column / line / pie / area / scatter (`help docx chart` for axis/legend/series styling). A PNG via `--type picture` is only a fallback for an exotic chart officecli can't build.\n\n### Hyperlinks and bookmarks\n\nExternal links go via `hyperlink`:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[2]\" --type hyperlink --prop url=\"https://example.com\" --prop text=\"our site\"\n```\n\n**Internal links** (to a bookmark) use `--prop anchor=bookmarkName` — not a `#fragment` in `url`:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[2]\" --type hyperlink --prop anchor=chapter1 --prop text=\"See Chapter 1\"\n```\n\nPairing a `PAGEREF` field with visible text is the alternative. See `help docx hyperlink` / `help docx bookmark`.\n\n### Sections and page setup\n\nDocument root `/` carries page setup (`pageWidth`, `pageHeight`, margins, in twips). Multi-section documents (landscape insert, columns) add a `section` break — see `help docx section`. Both camelCase (`pageWidth`, canonical) and lowercase alias (`pagewidth`) are accepted; prefer camelCase.\n\n```bash\nofficecli set \"$FILE\" / --prop pageWidth=12240 --prop pageHeight=15840 --prop marginTop=1440 --prop marginLeft=1440\n# Newspaper-style multi-column flow (columnSpace in twips; 720 = 0.5in):\nofficecli set \"$FILE\" / --prop columns=2 --prop columnSpace=720\n```\n\n### Forcing page breaks\n\nUse exactly one page-break mechanism per logical boundary. The default is `pageBreakBefore=true` on the target heading. Alternatively, insert one explicit `pagebreak` before the heading. Never combine both mechanisms for the same boundary: the resulting double break can create a blank page. Never set `pageBreakBefore` on `p[last()]` immediately after adding a `pagebreak`.\n\n```bash\n# Default: apply the break directly to the heading.\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Introduction\" --prop style=Heading1 --prop pageBreakBefore=true\n```\n\n`--prop break=newPage` is an alias for `pageBreakBefore=true`. Preview with `view html` and check for blank pages.\n\n### Report-level recipes\n\nPatterns that come up on every long-form report. Each has been executed and `validate`-passed.\n\n**(a) Rich cover page — hit the ≥ 60% filled floor.** Stack a confidentiality banner, title, subtitle, client/project/date block, and a key-themes strip, then force the next section onto a new page:\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"CONFIDENTIAL — CLIENT USE ONLY\" --prop align=center --prop size=9pt --prop color=C00000 --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Strategic Growth Review\" --prop style=Title --prop size=32pt --prop bold=true --prop align=center --prop font=Cambria --prop spaceAfter=8pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"FY26 Outlook and Scenario Planning\" --prop italic=true --prop size=16pt --prop align=center --prop spaceAfter=36pt\nofficecli add \"$FILE\" /body --type paragraph --prop text='Prepared for: Acme Corp. Leadership Team' --prop align=center --prop size=11pt\nofficecli add \"$FILE\" /body --type paragraph --prop text='Engagement: 2026-04 — 2026-06' --prop align=center --prop size=11pt --prop spaceAfter=36pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Key themes: 1) margin resilience, 2) EMEA expansion, 3) capital allocation.\" --prop align=center --prop italic=true --prop size=10pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1 --prop pageBreakBefore=true\n```\n\n**(b) Page X of Y footer — composite PAGE + NUMPAGES.** Add the footer paragraph, then three child ops build `Page <X> of <Y>` live. The official `help docx footer` recipe.\n\n```bash\nofficecli add \"$FILE\" / --type footer --prop type=default --prop text=\"Page \" --prop align=center --prop size=9pt\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type field --prop fieldType=page\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type run --prop text=\" of \"\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type field --prop fieldType=numpages\nofficecli get \"$FILE\" \"/footer[1]/p[1]\" --depth 1 | grep -o fldChar | wc -l   # expect ≥ 4; use grep -o ... | wc -l, NOT grep -c (single-line XML returns 1)\n```\n\n**(c) Header row with fill and white bold text.** Order matters — populate header cell text FIRST (runs don't exist in empty cells; a `set …/tc[N]/p[1]/r[1]` on an empty cell errors \"No r found\"), THEN cell fill, THEN run formatting:\n\n```bash\nofficecli add \"$FILE\" /body --type table --prop rows=5 --prop cols=4 --prop width=100%\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop header=true --prop c1=Quarter --prop c2=Revenue --prop c3=Growth --prop c4=Status\nfor col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[$col]\" --prop fill=1F4E79\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[$col]/p[1]/r[1]\" --prop bold=true --prop color=FFFFFF\ndone\nfor row in 3 5; do for col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[$row]/tc[$col]\" --prop fill=D9E2F3      # zebra stripe\ndone; done\n```\n\n**(d) Financial table — right-align numbers, bold totals, bottom border on total row.**\n\n```bash\nfor row in 2 3 4 5; do for col in 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[$row]/tc[$col]/p[1]\" --prop align=right\ndone; done\nfor col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[5]/tc[$col]/p[1]/r[1]\" --prop bold=true\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[4]/tc[$col]/p[1]\" --prop pbdr.bottom=\"single;6;000000;0\"\ndone\n```\n\n**(e) Cell with multiple bullets (SWOT / risk matrix).** `c1=\"a\\nb\"` gives a `<w:br/>` line break within **one** paragraph — fine for plain multi-line text, but bullets need separate paragraphs. Seed the first via `set c1=`, then `add paragraph` (with `listStyle=bullet`) under the cell per subsequent bullet:\n\n```bash\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop c1=\"Installed base of 18k enterprise seats\"\nofficecli add \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]\" --type paragraph --prop text=\"Margin structure above peer median\" --prop listStyle=bullet\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[1]\" --prop listStyle=bullet\n```\n\nIf the seeded line lands at the bottom, re-order: `officecli move \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[N]\" --index 0`.\n\n**(f) TOC without a field engine.** A CLI-only pipeline cannot calculate reliable page numbers. Keep the live TOC, set `updateFields=true`, and tell the recipient to open the document in Word or another compatible field engine. Do not replace it with guessed static page numbers.\n\n### Template delivery — separating Template Notes from end-user content\n\nHR / legal / vendor templates carry internal-only guidance (\"replace `{{CompanyName}}`\") that must NOT ship. Two working patterns:\n\n- **Trailing \"Template Notes\" section** under a clear `Heading 1` (\"Template Notes for HR Users\") with all instructions below it; before distribution, `remove` from the heading downward (locate with `query 'paragraph[style=Heading1]:contains(\"Template Notes\")'`).\n- **Bookmark-bounded internal section** between `__template_notes_start` / `_end` bookmarks; at delivery `raw-set` removes everything between the anchors.\n\nDelivery gate for templates: after removal, `query 'p:contains(\"Template Notes\")'` AND `query 'p:contains(\"{{\")'` both return empty. If a notes paragraph survives, a downstream employee reads internal language.\n\n### Advanced / specialty topics (skip if you are writing a report)\n\nReports, memos, letters, proposals, and HR templates don't need this. Keep reading only if your document is academic (equations, footnotes, bibliography), reviewed (comments, tracked changes), or marked (watermark).\n\n**Equations and footnotes.** `--type equation` takes LaTeX — `\\frac`, `\\sum`, Greek, `\\mathit`, `\\mathcal` all render. By default it creates a standalone `/body/oMathPara[N]` display block; pass `--prop mode=inline` with a paragraph parent path (`add \"/body/p[N]\" --type equation --prop formula=… --prop mode=inline`) to drop an inline `<m:oMath>` into running text. Footnotes auto-number by paragraph index. Bibliography hanging indent: `firstLineIndent=-720 indent=720` per entry.\n\n```bash\nofficecli add \"$FILE\" /body --type equation --prop formula=\"\\\\frac{a}{b} + \\\\sum_{i=1}^{n} x_i\"\nofficecli add \"$FILE\" \"/body/p[3]\" --type footnote --prop text=\"See Appendix A for methodology.\"\n```\n\n**Comments and tracked changes.** Bulk accept/reject: `set \"$FILE\" /revision --prop revision.action=accept` (or `--prop revision.action=reject`); narrow with a selector like `/revision[@author=Alice]` or `/revision[@type=ins]`. Locate individual changes with `query ins` and `query del` (`trackedchange` is not a selector). Create tracked changes on a run with `--prop revision.type=ins|del --prop revision.author=…` (`help docx run` for the full `revision.*` set — `format`/`moveFrom`/`moveTo` too). Add a comment: `add \"/body/p[4]\" --type comment --prop author=… --prop text=…`; reply-thread it with `--prop parentId=N` and mark it resolved with `set \"/comments/comment[N]\" --prop done=true` (resolve rather than delete to keep the audit trail — `query 'comment[done=false]'` then lists what's still open). Prop schema: `help docx comment` / `help docx run`.\n\n**Watermark.** `add / --type watermark --prop text=\"DRAFT\" --prop color=BFBFBF --prop opacity=0.8` in one command (default opacity 0.5); `set /watermark --prop opacity=…` adjusts it later.\n\n**When to switch skills.** Stay in docx for chapter drafts, ≤ 3 footnotes, ≤ 2 equations, no bibliography/cross-refs. Switch to **`academic-paper`** for citation styles (APA / Chicago / IEEE / GB 7714), in-text↔reference auto-linking, numbered equations with `\\ref`, \"List of Figures\", or auto-updating cross-refs. Switch to **`officecli-word-form`** when the document's purpose is **data capture** — fillable forms, contracts with user-fill slots, questionnaires, mail-merge templates (`<w:sdt>` content controls, `<w:ffData>`, `documentProtection=forms`).\n\n### Raw-set escape hatch (L1 / L2 / L3)\n\nThree tiers of precision; use the lowest that does the job.\n\n- **L1 — high-level props** (`--prop text=…`, `--prop style=Heading1`): your default. Covers 80%.\n- **L2 — dotted-attr fallback** (`pbdr.top=`, `ind.left=`, `shd.fill=`, `padding.top=`, `font.size=`): when L1 lacks the knob. Example: `--prop pbdr.bottom=\"single;6;1F4E79;0\"`. Emits schema-valid XML.\n- **L3 — `raw-set` with XML**: last resort, no schema protection. Use for internal hyperlinks, composite fields, and other shapes the typed verbs can't express (see XML appendix).\n\nBorders use the format `style;size;color;space`: `single;4;FF0000;1`. Hex colors never start with `#`: `FF0000`. Scheme color names (`accent1..6`, `dark1`/`dark2`, `light1`/`light2`, `hyperlink`) are accepted anywhere a hex color is — prefer hex for stable colors across themes.\n\n## QA (Required)\n\n**Assume there are problems — QA is a bug hunt, not a confirmation step.** Your first document is almost never correct; zero issues on first inspection means you weren't looking hard enough. Headings look fine until `view outline` shows an H3 directly under an H1; the footer shows \"Page 1\" until `get --depth 3` reveals a static run, not a field.\n\n### Minimum cycle before \"done\"\n\n1. `officecli view \"$FILE\" issues` — empty paras, missing alt text, formatting anomalies.\n2. `officecli view \"$FILE\" outline` — heading hierarchy (no H1 → H3 skips), TOC presence, section count.\n3. `officecli view \"$FILE\" text --max-lines 400` — typos, stray `\\$`/`\\t`/`\\n` literals, placeholder tokens.\n4. `officecli validate \"$FILE\"` — schema check (the Delivery Gate re-runs this on the closed, on-disk file).\n5. **Visual pass — whole document as a contact sheet** (vision-capable agents only — if you cannot interpret images, skip this step: steps 1–4 are your ceiling, and flag the document \"not visually verified\" at handoff). `officecli view \"$FILE\" screenshot --grid auto -o /tmp/sheet.png`, then Read it. `--grid auto` tiles **every page** into one image (auto column count; `--grid 4` to force) — you *see* pagination, blank pages, heading rhythm, lopsided margins, and TOC/cover placement, not just the DOM. Windows+Word renders each page through real Word; elsewhere HTML. If the screenshot fails, fall back to `view html` and flag cross-page breaks / alignment / rhythm as \"not visually verified\". Thumbnails only **locate**: confirm any fine call (column alignment, line spacing, indents, dark-on-dark, caption placement) on the suspect page at full resolution with `screenshot --page N` (no `--grid`; real Word on Windows). \"validate pass\" is not delivery; \"looks like a real document\" is.\n6. If anything failed, fix, then **rerun the full cycle** — one fix commonly creates another problem.\n\n### Delivery Gate (run before handing off — any failure = REJECT, do NOT deliver)\n\nCopy-paste, set `FILE`, and refuse to declare done until every gate prints OK.\n\n```bash\nFILE=\"your-file.docx\"\n\n# Gate 1 — schema.\nofficecli close \"$FILE\" 2>/dev/null\nofficecli validate \"$FILE\" | grep -q \"no errors found\" || { echo \"REJECT Gate 1: validate failed\"; exit 1; }\necho \"Gate 1 OK\"\n\n# Gate 2 — token leak (shell-escape / template tokens / literal \\$ \\t \\n). grep -c never false-PASSes.\nLEAK=$(officecli view \"$FILE\" text | grep -cE '(\\$[A-Za-z_]+\\$|\\{\\{[^}]+\\}\\}|<TODO>|xxxx|lorem|\\\\[\\$tn])')\n[ \"$LEAK\" -eq 0 ] && echo \"Gate 2 OK\" || { echo \"REJECT Gate 2: $LEAK leak line(s)\"; officecli view \"$FILE\" text | grep -nE '(\\$[A-Za-z_]+\\$|\\{\\{[^}]+\\}\\}|<TODO>|xxxx|lorem|\\\\[\\$tn])'; exit 1; }\n# A TOC placeholder is valid before a Word-compatible field engine updates it; confirm the TOC field and updateFields setting structurally instead.\n\n# Gate 3 — live PAGE field exists when a footer is expected.\nFLD=$(officecli query \"$FILE\" 'field[fieldType=page]' --json | jq '.data.results | length')\n[ \"$FLD\" -ge 1 ] && echo \"Gate 3 OK\" || { echo \"REJECT Gate 3: no live PAGE field\"; exit 1; }\necho \"Delivery Gate PASS\"\n```\n\n### Field / cached-value spot-check\n\nFields carry cached values that may be stale or empty at write time — confirm existence by **structure, not text**.\n\n- **Footer PAGE:** `get /footer[N] --depth 3` lists the begin / instrText / separate / cached / end run chain — ≥ 5 runs for one PAGE, ≥ 11 for composite \"Page X of Y\". A single run with text `\"Page\"` = field missing; re-add with `--prop field=page`.\n- **TOC:** `get /toc[1] --depth 2` shows field structure. Page numbers may read `1 1 1 1` or `Update field to see…` until recalculated (see §Table of Contents — set `updateFields=true`).\n- **MERGEFIELD:** `query 'field[fieldType=mergefield]'` — one per slot, no literal `{{name}}` elsewhere.\n\n### Honest limit\n\n`validate` catches schema errors, not design errors — a document can pass it with wrong heading hierarchy, fake-Heading-1 sizes, placeholder tokens as body text, or an empty first-page footer on a coverless document. The contact-sheet visual pass (`screenshot --grid`) and the field-structure check are how you catch what validation can't.\n\n### QA display notes (don't chase these)\n\n- `view text` shows `\"1.\"` for every numbered list item regardless of rendered number — actual output increments correctly.\n- `view issues` flags \"body paragraph missing first-line indent\" on cover paragraphs, centered headings, list items, bibliography entries — first-line indent is only required for APA/academic body text; on block-style professional documents these are expected.\n\n## Known Issues & Pitfalls\n\nWhen something \"looks broken\", attribute it before chasing: **[AGENT-ERROR]** the document is wrong (fix it) · **[RENDERER-BUG]** the document is correct, a viewer renders it differently (don't chase) · **[SKILL gap]** the skill didn't teach the rule (file an issue).\n\n### Renderer quirks (cross-viewer, [RENDERER-BUG] — don't chase)\n\nBefore calling a color/field/chart broken, open the file in the user's target viewer; if it looks correct there it's a viewer quirk.\n\n- **PAGE field may render literal \"Page\"** (no number) until recalculated — judge by `fldChar` presence, not the digit.\n- **TOC cached page numbers may read \"1 1 1 1\"** until F9.\n- **Pie / doughnut fill may collapse to one color** in some viewers (column/bar render fine).\n- **Form-control checkboxes may render double-boxed**; **OMML equation baselines** may shift across viewers (XML identical).\n\n### Common pitfalls\n\n| Pitfall | Correct approach |\n|---|---|\n| `--index` vs `[N]` | `--index` is 0-based; `[N]` paths are 1-based |\n| Multiple `add --index N` with the same N | Each insert shifts later content down; reusing N puts later items BEFORE earlier ones — insert in reverse order, or `move --after/--before` anchored on `paraId` |\n| Unquoted `[N]` in zsh/bash | Quote every path: `\"/body/p[1]\"` |\n| `[last]` as predicate | Must be `[last()]` with parens |\n| Raw twips in spacing | Use unit-qualified values: `12pt`, `0.5cm`, `1.5x` |\n| Empty paragraphs for spacing | Use `spaceBefore` / `spaceAfter` |\n| Row-level `set` for cell formatting | Row `set` only supports `height`, `header`, `c1..cN` text; format goes on the cell paragraph / run |\n| `listStyle` on a run | It's a paragraph property |\n| Indent via leading spaces | `indent=720` / `firstLineIndent=360` / `hangingIndent=720` (dotted `ind.left` / `ind.firstLine` also work) |\n| Cover page-number suppression via `set differentFirstPage=true` | UNSUPPORTED — add a first-type footer: `--type footer --prop type=first --prop text=\"\"` |\n| Need a fresh chapter page | Use `pageBreakBefore=true` on the heading; use an explicit `pagebreak` only as an alternative, never both |\n| Multiple bullet paragraphs in one cell | `c1=\"a\\nb\"` makes a `<w:br/>` line break (one paragraph); for separate bullet paragraphs use recipe (e) |\n| `raw-set` when dotted-attr would work | Prefer L2 dotted-attr over L3 raw-set |\n| Next paragraph inherits the previous Heading style | Set explicit `--prop style=Normal` on the following paragraph |\n| Modifying a file open in Word | Close it in Word first |\n| Echo into batch breaks on `$`/`'` | Heredoc with single-quoted delimiter: `cat <<'EOF' \\| officecli batch …` |\n\n## Raw-set XML appendix (L3 patterns)\n\n`raw-set` injects literal OOXML — no schema protection. Element order in `<w:pPr>`: `pStyle`, `numPr`, `spacing`, `ind`, `jc`, `rPr` (last). Smart quotes as entities (`&#x2018;`/`&#x2019;`/`&#x201C;`/`&#x201D;`). Add `xml:space=\"preserve\"` to any `<w:t>` with leading/trailing spaces. RSIDs are 8-digit hex. Use \"Claude\" as the author for tracked changes/comments unless the user names another.\n\n**Tracked-change insertion / deletion** — prefer the high-level `--prop revision.type=ins|del` on a run; raw-set only for what the typed path can't express (rejecting/restoring another author's change, below). Replace the whole `<w:r>…</w:r>`, never inject tags inside a run; copy the original `<w:rPr>` into both to preserve formatting. Inside `<w:del>` use `<w:delText>` (and `<w:delInstrText>` for instructions):\n\n```xml\n<w:r><w:t>The term is </w:t></w:r>\n<w:del w:id=\"1\" w:author=\"Claude\" w:date=\"2026-01-01T00:00:00Z\"><w:r><w:delText>30</w:delText></w:r></w:del>\n<w:ins w:id=\"2\" w:author=\"Claude\" w:date=\"2026-01-01T00:00:00Z\"><w:r><w:t>60</w:t></w:r></w:ins>\n<w:r><w:t> days.</w:t></w:r>\n```\n\nWhen deleting ALL content of a paragraph/list item, also mark the paragraph mark deleted (`<w:del/>` inside `<w:pPr><w:rPr>`) — otherwise accepting changes leaves an empty paragraph. To **reject another author's insertion**, nest your `<w:del>` inside their `<w:ins>`; to **restore their deletion**, add a `<w:ins>` after it (don't modify theirs).\n\n**Internal hyperlink to a bookmark** (prefer the high-level `--prop anchor=` path; raw-set only for custom run styling the command can't express):\n\n```xml\n<w:hyperlink w:anchor=\"chapter1\"><w:r><w:rPr><w:rStyle w:val=\"Hyperlink\"/></w:rPr><w:t>See Chapter 1</w:t></w:r></w:hyperlink>\n```\n\n**Composite field in one run** (e.g. two fields the single-command path can't compose) — the `fldChar begin / instrText / separate / value / end` chain:\n\n```xml\n<w:r><w:fldChar w:fldCharType=\"begin\"/></w:r>\n<w:r><w:instrText xml:space=\"preserve\"> PAGE </w:instrText></w:r>\n<w:r><w:fldChar w:fldCharType=\"separate\"/></w:r>\n<w:r><w:t>1</w:t></w:r>\n<w:r><w:fldChar w:fldCharType=\"end\"/></w:r>\n```\n\n**Comment markers** are siblings of `<w:r>`, NEVER inside one (reply threading and resolved-state are high-level — `--prop parentId=`/`done=`, see Comments above):\n\n```xml\n<w:commentRangeStart w:id=\"0\"/><w:r><w:t>annotated text</w:t></w:r><w:commentRangeEnd w:id=\"0\"/>\n<w:r><w:rPr><w:rStyle w:val=\"CommentReference\"/></w:rPr><w:commentReference w:id=\"0\"/></w:r>\n```\n\nForce field recalc on open with `officecli set \"$FILE\" /settings --prop updateFields=true` (writes `<w:updateFields w:val=\"true\"/>`; covers the layout-dependent fields PAGE / PAGEREF / NUMPAGES / TOC page numbers — no raw-set needed). For SEQ numbering, prefer `set / --prop recalcFields=seq`, which writes correct cached values now without waiting on Word.\n\n### Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <verb> <element>`, `--json` for agents. Help is the authoritative schema; this skill is the decision guide.\n","skills/officecli-academic-paper/SKILL.md":"---\nname: officecli-academic-paper\ndescription: \"Use this skill to build academic-style .docx output: journal / conference / thesis chapters carrying formal citation style (APA, Chicago, IEEE, MLA), numbered equations, figure & table cross-references, footnotes/endnotes, bibliography, or multi-column journal layout. Trigger on: 'research paper', 'journal paper', 'conference paper', 'manuscript', 'thesis', 'APA', 'MLA', 'Chicago', 'IEEE two-column', 'bibliography', 'hanging indent', 'citation style', 'abstract + keywords', 'equation numbering', 'cross-reference', paper with footnotes/endnotes. Output is a single .docx.\"\n---\n\n# OfficeCLI Academic Paper Skill\n\n**This skill is a scene layer on top of `officecli-docx`.** Every docx hard rule — style architecture, heading hierarchy, shell quoting, page-break rules, live PAGE field, Delivery Gate, renderer quirks — is inherited, not re-taught. This file adds only what academic papers need on top: citation styles, equations, SEQ / PAGEREF cross-refs, multi-column journal layout, bibliography hanging indent, abstract/keywords/affiliation block.\n\nWhen the docx base rules cover it, the text here says `→ see docx v2 §X`. Read docx v2 first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what an academic paper requires, not every command flag.** When a prop name, enum value, or field instruction is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help docx                          # All docx elements\nofficecli help docx <element>                # Full schema (e.g. section, equation, field, footnote)\nofficecli help docx <element> --json         # Machine-readable\n```\n\nHelp is pinned to the installed CLI version. **When this skill and help disagree, help wins.** Every `--prop X=` in this file has been grep-verified against `officecli help docx <element>` — if help adds / renames a prop in a later version, trust help.\n\n## Mental Model & Inheritance\n\n**Inherits docx v2.** You should have read `skills/officecli-docx/SKILL.md` first. This skill assumes you know how to add paragraphs, set styles, build tables, insert images, manage TOC/footer/headers, force page breaks, and run the Delivery Gate. If any of those are unfamiliar, open a second session on docx v2 before continuing.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see docx v2 §Shell & Execution Discipline. The same rules apply here verbatim — quote `[N]` paths, single-quote any value containing `$` (including `$2.8B` in a body paragraph or `@` DOIs), never hand-write `\\$ \\t \\n` in executable examples, one command at a time. Academic-paper examples below use `$FILE` as a shell variable (`FILE=\"thesis.docx\"`).\n\n## What \"academic\" means here (identity)\n\nAn academic paper is a docx with a **scholarly layer** on top: verifiable citations, precise equations, cross-refs that stay in sync, a formatted reference list. The base docx rules still apply; academic adds six deltas:\n\n1. **Citation style is a contract.** APA / Chicago / IEEE / MLA each dictate author format, date placement, reference-list order, in-text marker shape. Pick one at the start; every later decision (hanging indent, footnote vs parenthetical, `[1]` vs `(Smith, 2024)`) follows.\n2. **Equations are first-class content** — inline `oMath` inside prose, display `oMathPara` as standalone blocks, optionally numbered.\n3. **Figures and tables auto-number.** `SEQ Figure` / `SEQ Table` fields count them; `PAGEREF` links \"see Fig. 2\" to its live page number.\n4. **Bibliography uses hanging indent** (first line flush left, continuation lines indented). Not first-line indent. Not left indent alone. Hanging.\n5. **Abstract / keywords / affiliation block** is a first-page three-piece, not a cover in the marketing sense. Block-style abstract, no first-line indent, no decoration.\n6. **Multi-column layout** appears in IEEE / ACM / Nature / many journals: single-column abstract + two-column body.\n\n### Reverse handoff — when to go BACK to docx\n\nStay in **docx v2** for white papers, policy briefs, technical reports, HR templates — anything without a venue / citation style. Use **this skill** only when the document will carry at least TWO of: citation-style biblio, equations, SEQ/PAGEREF cross-refs, multi-column, abstract + keywords block.\n\n## Workflow — 5 verbs\n\n1. **Read the venue spec.** APA 7 / Chicago 17 / IEEE / MLA 9 / journal-specific. Line spacing, font, citation shape, biblio sort order — everything downstream follows from this one decision.\n2. **Plan the sections.** Abstract → keywords → introduction → methods → results → discussion → conclusion → references. Estimate heading count for TOC decision (3+ headings = add a TOC, see docx v2 §Table of Contents).\n3. **Set styles up front.** Heading1 / Heading2 / Heading3 / Caption / AbstractTitle / Bibliography. Define all styles BEFORE any content (→ see docx v2 §Paragraphs and styles — same rule here, same failure mode if skipped).\n4. **Build body in order.** Cover / title block → abstract → keywords → TOC (if needed) → body sections in reading order → figures / tables with SEQ captions → bibliography → footnotes are added last by paragraph path.\n5. **QA — Delivery Gate.** Inherit docx v2 Gates 1-3, then add academic Gates 4-5 below.\n\n## Requirements (academic floor on top of docx v2)\n\nEverything in docx v2 §Requirements for Outputs applies. On top of that, academic papers MUST meet these additional rules:\n\n### Typography and spacing (venue-aware)\n\n- **Font.** Times New Roman 11-12pt body (default) or venue-specified (IEEE uses Times 10pt 2-col; APA allows Calibri 11pt). Same body font throughout; no decorative heading fonts.\n- **Heading hierarchy.** H1 = 20pt bold, H2 = 14pt bold, H3 = 12pt bold italic, body = 11-12pt. (Same numbers as docx v2 — restated because academic papers never rely on Word defaults.)\n- **Line spacing.** APA 7 = 2x (double). Chicago / IEEE / most journals = 1.5x. Never below 1.15x. Set on body paragraphs and on References.\n- **Margins.** 1 inch (1440 twips) all sides unless the venue says otherwise (some journals require 1.25in left for binding — check the spec).\n\n### Abstract, bibliography, caption placement\n\n- **Abstract is block-style.** NO `firstLineIndent`. Use `spaceAfter=12pt` for paragraph separation. If `view issues` reports \"body paragraph missing first-line indent\" on an Abstract paragraph, it's a false positive — ignore.\n- **Bibliography uses hanging indent.** Each entry is one paragraph with `indent=720 hangingIndent=720` (left indent 0.5\", first-line reversed by same amount). First line flush left; wraps indent under author name.\n- **Figure captions go BELOW the figure.** Table captions go ABOVE the table. This is the single rule most non-academics get wrong — APA, Chicago, IEEE, MLA all agree on it.\n- **Citation round-trip.** Every in-text citation key must resolve to an entry in the reference list. Delivery Gate 4 verifies.\n- **SEQ presence.** Any paper with numbered figures or tables must carry live `SEQ Figure` / `SEQ Table` fields (not hardcoded \"Figure 1\" text that drifts when you insert a new figure mid-document). Delivery Gate 5 verifies.\n\n### Cover / first-page block\n\nAcademic covers differ from professional covers. Minimum elements: title (centered, 20-22pt bold), author(s), affiliation, submission target or journal, date, abstract, keywords. The \"60% fill\" rule from docx v2 §Visual delivery floor still applies — a three-line cover with half a page of whitespace is a fail. See §Abstract / keywords / affiliation block below for the first-page recipe.\n\n### Section numbering convention (STYLE-DEPENDENT — do not apply blindly)\n\nAcademic section numbers are **part of the heading text**, not computed via list numbering. `officecli`'s `numId`/`listStyle` mechanism is fragile across Heading1 re-use, so hand-write the prefix. BUT the prefix shape varies by style — DO NOT use the same form for all four:\n\n| Style | H1 format | H2 format | Example |\n|---|---|---|---|\n| **APA 7** | **UNNUMBERED centered bold** | Unnumbered left-aligned bold | `Introduction` / `Methods` (centered) |\n| **Chicago** | `\"N. Title\"` left-aligned | `\"N.M Title\"` | `1. Introduction`, `2.1 Policy Formation` |\n| **IEEE** | `\"N. TITLE\"` ALL CAPS + Roman numerals | `A. Subtitle` title case | `I. INTRODUCTION`, `II. RELATED WORK`, `A. Datasets` |\n| **MLA 9** | Unnumbered left-aligned bold | Same | `Literature Review` (no prefix) |\n\nAPA 7 L1 headings are **centered, bold, unnumbered**; L2 are flush-left bold; L3 flush-left bold italic; L4/L5 run-in. Do NOT prefix APA headings with `1. / 2.` — that is Chicago/IEEE convention. IEEE wants ALL CAPS with Roman numerals (`I. INTRODUCTION`); inside each section, use `A./B./C.` sub-headings (title case). Arabic-numbered body sections are Chicago-style only.\n\n**Exception for all four**: References / Bibliography / Works Cited / Acknowledgments are unnumbered regardless of style — omit the `N.` prefix.\n\n## Quick Start — minimal APA paper\n\n```bash\nFILE=\"paper.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" / --prop defaultFont=\"Times New Roman\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote Work and Team Cohesion\" --prop align=center --prop size=20pt --prop bold=true --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen\" --prop align=center --prop size=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Department of Psychology, Stanford University\" --prop align=center --prop size=11pt --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Abstract\" --prop align=center --prop size=14pt --prop bold=true --prop spaceBefore=12pt --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"This study examines remote-work adoption on team cohesion across 18 months...\" --prop size=12pt --prop lineSpacing=2x --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Keywords: remote work, team cohesion, psychological safety\" --prop italic=true --prop size=11pt --prop spaceAfter=18pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"1. Introduction\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote-work research (Smith, 2024) has expanded since 2020...\" --prop size=12pt --prop lineSpacing=2x --prop firstLineIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"References\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Smith, J. (2024). Remote work and cohesion. Journal of Applied Psychology, 109(3), 412-430.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=10pt --prop field=page\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nTen-line skeleton. Real papers grow by adding more body paragraphs, more bibliography entries (each with the same `indent=720 hangingIndent=720` pair), figures / tables with captions, and a TOC if there are 3+ Heading1s. The Quick Start validates clean; the sections below elaborate each dimension.\n\n## Citation style recipes\n\nFour mainstream families. Pick one at project start; every downstream decision follows. **Per-style decision table:**\n\n| Style | In-text shape | Reference list order | Body line spacing | Footnotes? |\n|---|---|---|---|---|\n| APA 7 | `(Smith, 2024)` or `Smith (2024)` | Alphabetical by author | 2x (double) | Rare (content notes only) |\n| Chicago 17 (Notes-Bib) | Superscript footnote number | Alphabetical by author | 1.5x-2x | **Primary** (full citation in footnote) |\n| IEEE | `[1]`, `[2]`, ..., `[N]` | Order of first citation | 1.15x-1.5x, 2-col | Rare |\n| MLA 9 | `(Smith 412)` page-number | Alphabetical by author, \"Works Cited\" | 2x | Rare |\n\nShared defaults across all four: reference-list paragraphs use `indent=720 hangingIndent=720` (hanging indent 0.5\"); add a live TOC if 3+ Heading1s (→ see docx v2 §Table of Contents); set `updateFields=true` and report TOC page numbers as uncomputed until a Word-compatible field engine updates them.\n\n### APA 7 (social sciences — psychology, education, management)\n\n- In-text: `(Author, Year)` or `Author (Year)` for narrative. Page number required on direct quotes: `(Smith, 2024, p. 15)`. Three+ authors: `(Smith et al., 2024)` after first citation.\n- Reference list order: **alphabetical by first author's surname**. Title caps: sentence case for article titles, title case for journal names (italic).\n- Reference shape: `Author, A. A., & Co-Author, B. B. (Year). Title of article. Journal Name, Volume(Issue), pages.` DOI preferred over URL; present as https URL, not `doi:` prefix.\n- Double-space everything (`lineSpacing=2x`) including abstract and references. Body first-line indent = 0.5\" (`firstLineIndent=720`).\n\n```bash\n# Body paragraph with parenthetical citation\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote work adoption accelerated during the pandemic (Kramer & Kramer, 2020).\" --prop size=12pt --prop lineSpacing=2x --prop firstLineIndent=720\n# Reference entry with hanging indent\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Kramer, A., & Kramer, K. Z. (2020). The potential impact of the Covid-19 pandemic on occupational status. Journal of Vocational Behavior, 119, 103442.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\n# DOI hyperlink appended to the reference paragraph\nofficecli add \"$FILE\" \"/body/p[last()]\" --type hyperlink --prop url=\"https://doi.org/10.1016/j.jvb.2020.103442\" --prop text=\"https://doi.org/10.1016/j.jvb.2020.103442\"\n```\n\nQA: `officecli query \"$FILE\" 'paragraph[hangingIndent]'` returns every reference entry; zero references with first-line indent instead of hanging.\n\n### Chicago 17 — Notes-Bibliography (humanities — history, philosophy, religion)\n\n- In-text: superscript footnote number; full citation in the first footnote (`Timothy Brook, The Troubled Empire (Cambridge, MA: Harvard UP, 2010), 142.`); **shortened form** thereafter (`Brook, Troubled Empire, 150.`).\n- **Repeat-citation rule (Chicago 17, op. cit. deprecated):**\n  - **Immediately-consecutive** citation of **the same source, same page** → `Ibid.`\n  - **Immediately-consecutive, different page** of same source → `Ibid., 22.`\n  - Non-consecutive repeat → **shortened form** (`Brook, Troubled Empire, 150.`), NOT `op. cit.`. Chicago 17 drops `op. cit.` — use shortened form every time except for immediate repeats.\n- Bibliography at end, **alphabetical by first author's surname** (\"Brook, Timothy.\"), hanging indent. Footnote body renders at the viewer's footnote default (typically 10pt); bibliography entries 12pt. (The `footnote` element exposes only `text` — size is not settable per-footnote; trust renderer defaults.)\n- Typical split for primary-source-heavy papers: `Primary Sources` and `Secondary Sources` as two Heading2s under a single `Bibliography` Heading1. Book titles italic in both footnotes and bibliography.\n- Chicago also has an Author-Date variant used in the sciences — if the venue specifies Chicago Author-Date, fall back to the APA recipe and change only the punctuation (no comma between author and year: `(Smith 2024)`).\n\n```bash\n# Body paragraph that will anchor a footnote, then the footnote itself\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"The Ming dynasty's 海禁 policy shaped coastal trade for two centuries.\" --prop size=12pt --prop lineSpacing=1.5x --prop firstLineIndent=720\nofficecli add \"$FILE\" \"/body/p[last()]\" --type footnote --prop text=\"Timothy Brook, The Troubled Empire: China in the Yuan and Ming Dynasties (Cambridge, MA: Harvard University Press, 2010), 142.\"\n# Next footnote — shortened form\nofficecli add \"$FILE\" \"/body/p[last()]\" --type footnote --prop text=\"Brook, Troubled Empire, 150.\"\n# Bibliography section split — primary sources first\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Bibliography\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Primary Sources\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Ming Shilu 明實錄. Taipei: Academia Sinica, 1966.\" --prop size=12pt --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Secondary Sources\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Brook, Timothy. The Troubled Empire: China in the Yuan and Ming Dynasties. Cambridge, MA: Harvard University Press, 2010.\" --prop size=12pt --prop indent=720 --prop hangingIndent=720\n```\n\nQA: `officecli query \"$FILE\" 'footnote'` count ≥ body-paragraph citation count.\n\n### IEEE (engineering — transactions, conference proceedings)\n\n- In-text: `[1]`, `[2]`. Numbered in **order of first appearance**, not alphabetical. Reuse the same number for repeat citations. `[1, p. 15]` for page refs, `[1]-[3]` for a range.\n- Reference entry starts with the bracketed number: `[1] A. Smith and B. Jones, \"Title,\" IEEE Trans. X, vol. 5, no. 3, pp. 1-10, 2024, doi: ...`. Authors are initial-first; journal names abbreviated per IEEE list (`IEEE Trans. Neural Netw.`, not full name).\n- Body is **two-column** (see §Multi-column below). Abstract is single-column above the fold, 10pt, 1.15x line spacing, typically 200-250 words.\n- First-line indent on body paragraphs = 0.2\" (`firstLineIndent=288` twips ≈ 14pt). Smaller than APA's 0.5\" because the 2-col width is narrower.\n- **Section headings: ALL CAPS with Roman numerals** — `I. INTRODUCTION`, `II. RELATED WORK`, `III. METHOD`. Sub-sections `A. Datasets`, `B. Baselines` in title case. Do NOT use `1. Introduction` (Arabic) for IEEE — that is Chicago style.\n- **Tables are numbered Roman**: `Table I`, `Table II`, `Table III`. Figures remain Arabic (`Fig. 1`, `Fig. 2`). `recalcFields=seq` writes Arabic cached values for both — for IEEE Roman tables, either patch the cached `<w:t>` to Roman manually after recalc, or accept Arabic and note it in the cover letter.\n\n```bash\n# Body citing reference 1\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Attention-based anomaly detection has been applied to industrial sensor data [1], [2].\" --prop size=10pt --prop lineSpacing=1.15x\n# Reference list entry — number in the text\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"[1] A. Smith and B. Jones, \\\"Attention for anomaly detection,\\\" IEEE Trans. Neural Netw., vol. 35, no. 2, pp. 412-430, 2024.\" --prop size=10pt --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"[2] C. Lee, \\\"Time-series anomaly survey,\\\" in Proc. ICML, 2023, pp. 1200-1215.\" --prop size=10pt --prop indent=720 --prop hangingIndent=720\n```\n\nQA: the highest `[N]` in body must equal the number of reference-list entries. Grep: `officecli view \"$FILE\" text | grep -oE '\\[[0-9]+\\]' | sort -u | tail -5`.\n\n### MLA 9 (literature, languages, cultural studies)\n\nDiff vs APA: in-text is `(Author Page)` **no comma** (e.g. `(Smith 412)`); direct quotes always carry the page number. Reference section titled **Works Cited** (not References / Bibliography). Entries alphabetical by surname, hanging indent, 2x spacing, nine \"core elements\" separated by periods: `Author. Title. Container, Other Contributors, Version, Number, Publisher, Date, Location.` — skip any that don't apply. Book titles italic; article titles in quotes. Otherwise identical to APA paragraph setup.\n\n## Equations (OMML — inline vs display)\n\n`--type equation` parses a LaTeX-ish formula into OMML. Two modes, selected by `--prop mode=`:\n\n| Mode | XML | Visual | Use |\n|---|---|---|---|\n| `display` (default) | `<m:oMathPara>` at `/body` | Standalone centered block | Numbered equations, theorem statements |\n| `inline` | `<m:oMath>` appended to a run inside a paragraph | Runs with the text | `if $x > 0$` style in prose |\n\n```bash\n# Display equation (own paragraph, centered) — explicitly set mode=display for clarity\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"x^2 + y^2 = z^2\"\n# Display equation with Greek / subscript / integral — verify rendering below\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"\\\\lambda_1 + \\\\alpha\"\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"\\\\frac{1}{2\\\\pi} \\\\int_0^{\\\\infty} e^{-x^2} dx\"\n# Inline equation INSIDE prose — required whenever variables like x_{t+1}, \\lambda, etc. appear in a body paragraph:\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Given the weight \" --prop size=11pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type equation --prop mode=inline --prop formula=\"W_t\"\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\" we define the loss...\"\n```\n\n**Verify equations render as OMML math**, not plain-text LaTeX tokens. After `close`, run:\n```bash\nofficecli view \"$FILE\" text | head -20       # λ₁ + α, ∫₀∞, x² must appear as unicode math (verified renders)\nofficecli raw \"$FILE\" /document | grep -c '<m:oMathPara'   # ≥ 1 per display equation\n```\nIf the body prose contains raw `lambda_1`, `x_{t+1}`, `\\alpha` or similar plain-text tokens (i.e., you typed them into a `paragraph --prop text=` instead of wrapping with `--type equation --prop mode=inline`), downstream viewers will render them as literal ASCII. **Rule: every mathematical variable / Greek letter / subscript in prose goes through `--type equation mode=inline`, never through `paragraph --prop text=`.**\n\n**LaTeX subset pitfalls** (non-negotiable):\n\n1. `\\left(...\\right)` / `\\left[...\\right]` with a sub/superscript **inside** the delimiters → parse error (`Error: cast object … Subscript`). An OUTER script (`\\left(x+y\\right)^2`) is fine; plain `(`, `)`, `[`, `]` always work and OMML auto-sizes them in display mode.\n2. `move` on `/body/oMathPara[N]` reorders the display equation (it repositions the wrapping paragraph). `--before <path>` may leave a stray empty paragraph; prefer `--index` or `--after` for a clean reorder.\n\n**Equation numbering** — no native `\\eqno`. The journal-standard layout is **one line**: equation centered, number flush-right at the column edge (`Y = A(X) ⊗ X      (1)`). Build it with two paragraph **tab stops** — a `center` tab at the column mid-point and a `right` tab at the column right edge — then lay out `[tab] equation(inline) [tab] (1)` in a single paragraph. Do NOT use a centered display equation followed by a separate right-aligned `(1)` line — that splits the number onto its own line and is the most common reason agents fail to reproduce the expected look.\n\n```bash\n# Tab positions depend on the COLUMN width (twips). Default blank doc = A4, 3.18cm margins\n#   → text width = 8300 twips.\n#   single column: center tab = 4150, right tab = 8300\n#   two columns (default 720-twip gutter): col = (8300-720)/2 = 3790 → center 1895, right 3790\n# (other page size / margins: text width = pageWidth - marginLeft - marginRight; recompute.)\nofficecli add \"$FILE\" /body --type paragraph                                   # the equation paragraph (say it lands at p[N])\nofficecli add \"$FILE\" \"/body/p[N]\" --type tab --prop pos=4150 --prop val=center\nofficecli add \"$FILE\" \"/body/p[N]\" --type tab --prop pos=8300 --prop val=right\nofficecli add \"$FILE\" \"/body/p[N]\" --type run --prop text=$'\\t'                 # tab → jump to center\nofficecli add \"$FILE\" \"/body/p[N]\" --type equation --prop mode=inline --prop formula='Y = A(X) \\otimes X'\nofficecli add \"$FILE\" \"/body/p[N]\" --type run --prop text=$'\\t(1)'             # tab → jump to right edge, then the number\n```\n\nFor a two-column section just use the two-column tab positions (1895 / 3790); the same paragraph then centers the equation within its column with `(1)` at the column's right edge. Schema: `officecli help docx tab`.\n\n**Do NOT place `--type equation` directly on a table cell `tc[N]` path** — it is rejected with a guard error (`table cells only accept paragraphs, tables, or SDTs`), so no bad XML is written. Target `tc[N]/p[1]` with `mode=inline` if you need equations in cells.\n\nFull equation schema: `officecli help docx equation`.\n\n## Figures, tables, and cross-references (SEQ + PAGEREF)\n\nTwo primitives, both **native fieldTypes** (`officecli help docx field` for the enum): `seq` for auto-numbered caption counters, `pageref` for \"see Fig. 2 on page 7\" back-references. Native fields insert with an unevaluated cached result; a single `set \"$FILE\" / --prop recalcFields=seq` (see §SEQ numbering below) fills the real numbers in document order — no per-field patching.\n\n### SEQ auto-numbering — figures and tables\n\nA SEQ field is a counter with a name (`identifier`). Every `SEQ Figure` increments the Figure counter on **recalc**; every `SEQ Table` increments the Table counter.\n\n**SEQ cached values — one command, after all captions are added.** A freshly-added SEQ field has no evaluated cached number; `view text` shows the `#OCLI_NOTEVAL!{SEQ Figure}` sentinel (and `Format[\"evaluated\"]=false`) until you recalc. **Do NOT patch each field by hand.** Once every figure/table caption is in place, run once:\n```bash\nofficecli set \"$FILE\" / --prop recalcFields=seq\n```\nIt counts `SEQ Figure` / `SEQ Table` fields in body document order and writes the real cached values (`Figure 1 / Figure 2 / Figure 3`), flipping `evaluated` true. Re-run it after inserting a caption mid-document so the numbers stay in sync. (Heading-relative `\\s` and SEQ inside headers/footers defer to Word's own recompute — see `help docx document`.) Verify: `officecli view \"$FILE\" text` shows distinct ascending numbers.\n\n```bash\n# Figure with caption BELOW the image. Caption = \"Figure <seq>: title\" + optional bookmark for cross-ref.\nofficecli add \"$FILE\" /body --type picture --prop src=arch.png --prop width=5in\nofficecli set \"$FILE\" \"/body/p[last()]/r[last()]\" --prop alt=\"Model architecture: attention over time-series sensors\"\n# Caption paragraph (below the figure, per academic convention)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Figure \" --prop style=Caption --prop size=10pt --prop italic=true --prop align=center\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=seq --prop identifier=Figure\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\": Attention-based anomaly detection model.\"\n# Bookmark the caption so other paragraphs can PAGEREF it\nofficecli add \"$FILE\" /body --type bookmark --prop name=fig_arch\n# (Numbers are filled later by a single `set / --prop recalcFields=seq`, after all captions exist.)\n```\n\n### PAGEREF — cross-reference by bookmark\n\n```bash\n# Cross-ref paragraph: \"see Figure 1 on page X\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"As shown in Figure 1 (see page \" --prop size=11pt --prop lineSpacing=1.5x\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=pageref --prop name=fig_arch\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\").\"\n```\n\n### Tables — caption ABOVE\n\n```bash\n# Caption first (ABOVE the table), THEN the table\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Table \" --prop style=Caption --prop size=10pt --prop italic=true --prop spaceAfter=6pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=seq --prop identifier=Table\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\": Participant demographics (N=47).\"\nofficecli add \"$FILE\" /body --type table --prop rows=5 --prop cols=4 --prop width=100%\n# ... fill header + rows per docx v2 §Tables\n```\n\n### Verify SEQ + PAGEREF fields landed\n\n```bash\n# At least one SEQ Figure or SEQ Table in the body document part\nofficecli raw \"$FILE\" /document | grep -c 'w:instrText[^>]*>[^<]*SEQ'   # expect ≥ 1\nofficecli raw \"$FILE\" /document | grep -c 'w:instrText[^>]*>[^<]*PAGEREF' # 0 ok if no cross-refs\n```\n\nLive fields carry **cached values** that render stale until a human presses F9 in Word. Expect \"Figure 1\" to show as `1`, `2`, ... immediately after recalc; before recalc, some viewers render `0` or blank. Judge field presence by `fldChar` existence, not by visible digit (→ see docx v2 §Field / cached-value spot-check).\n\n## Footnotes vs endnotes\n\n**Footnote** — sits at the bottom of the page where its anchor paragraph lives. Used for source citations in Chicago Notes-Bib, content asides in any style.\n\n**Endnote** — sits at the end of the document (or before the bibliography). Used by some venues in place of footnotes, or for long contextual notes that would clutter the page.\n\n```bash\n# Footnote anchored to paragraph N\nofficecli add \"$FILE\" \"/body/p[3]\" --type footnote --prop text=\"Smith et al. reported similar findings in their 2023 review.\"\n# Endnote — anchored to paragraph N (like footnote); lands at /endnote[@endnoteId=N]\nofficecli add \"$FILE\" \"/body/p[3]\" --type endnote --prop text=\"Extended derivation of equation (4) is available at the project repository.\"\n```\n\nBoth appear as empty-string runs in `view annotated` output (`r[N] \"\"`) — the run carries a `<w:footnoteReference>` XML element, not visible text. Confirm insertion with `officecli query \"$FILE\" 'footnote'` or `officecli get \"$FILE\" \"/footnotes/footnote[N]\"`. Footnotes do NOT shift paragraph indices; add them in any order after body content is in place. Full schema: `officecli help docx footnote` / `officecli help docx endnote`.\n\n## Bibliography section\n\nEvery academic paper ends with a reference list. The name of the section depends on the style (**References** for APA / IEEE / Chicago Author-Date; **Bibliography** for Chicago Notes-Bib; **Works Cited** for MLA). Each entry is a separate paragraph with **hanging indent**.\n\n```bash\n# Section heading — same as body Heading1 (excluded from body numbering by convention)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"References\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\n# Each entry: hanging indent 720 twips (0.5\"), with indent=720 as the partner (first line flush, wraps indented)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Smith, J. (2024). Remote work and cohesion. Journal of Applied Psychology, 109(3), 412-430.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\n# DOI hyperlink on its own run appended to the entry paragraph\nofficecli add \"$FILE\" \"/body/p[last()]\" --type hyperlink --prop url=\"https://doi.org/10.1037/apl0001123\" --prop text=\"https://doi.org/10.1037/apl0001123\"\n```\n\nVerified: `--prop indent=720 --prop hangingIndent=720` is the canonical hanging-indent pair per `officecli help docx paragraph`. The old `ind.firstLine=-720` form (negative first-line indent) is NOT canonical and fails schema on emit — → see docx v2 §Schema-invalid-on-emit.\n\n**Round-trip QA.** Count in-text citation markers (APA `(Author, Year)`, IEEE `[N]`, MLA `(Author N)`) vs reference-list entries. See Delivery Gate 4 below. Every cited key must resolve; every listed entry should be cited at least once.\n\n## Multi-column (IEEE journal two-column recipe)\n\nIEEE and many engineering / physics journals render body text in two columns with a single-column abstract above. The mechanism: a section break with `type=continuous` and `columns=2`, then another section break at the end to **revert** to single-column.\n\n**The reversion step is not optional.** Without it, the rest of the document — including references — renders as two columns. This is the single most common multi-column failure.\n\n```bash\nFILE=\"ieee.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\n# 1. Title, authors, affiliation — single-column (the default first section)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Attention-Based Anomaly Detection for Industrial Time Series\" --prop align=center --prop size=18pt --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen, Bob Martinez\" --prop align=center --prop size=11pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Department of CS, Stanford University\" --prop align=center --prop size=10pt --prop spaceAfter=18pt\n\n# 2. Abstract — still single-column, block-style\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Abstract\" --prop align=center --prop size=12pt --prop bold=true --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"We present an attention-based model for detecting anomalies in industrial sensor time series...\" --prop size=10pt --prop lineSpacing=1.15x --prop spaceAfter=12pt\n\n# 3. Section break + two-column from here on\n#    `/section[last()]` resolves to the final section (like p[last()]); an explicit /section[N] also works.\nofficecli add \"$FILE\" /body --type section --prop type=continuous\nSECTION_COUNT=$(officecli query \"$FILE\" section --json | jq '.data.results | length')\n# After the add, SECTION_COUNT should be 2 — [1] is pre-break, [2] is post-break (2-col body area).\nofficecli set \"$FILE\" \"/section[2]\" --prop columns=2 --prop columnSpace=1cm\n\n# 4. Body — IEEE wants Roman numerals + ALL CAPS section titles (P1.2).\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"I. INTRODUCTION\" --prop style=Heading1 --prop size=10pt --prop bold=true\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Industrial anomaly detection has been studied since [1]...\" --prop size=10pt --prop lineSpacing=1.15x --prop firstLineIndent=360\n\n# 5. At the end of 2-column body, ANOTHER section break + revert to single column for references / appendices\n# (If you want references in 2-col too, skip step 5 — but most IEEE papers use 2-col for references as well.)\n# officecli add \"$FILE\" /body --type section --prop type=continuous\n# Use /section[last()] for the final section, or re-count for an explicit /section[N]:\n# officecli set \"$FILE\" \"/section[3]\" --prop columns=1\n\n# 6. Footer, close, validate\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=9pt --prop field=page\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\n**Visual verify.** Run `officecli view \"$FILE\" html` and Read the returned HTML to audit the rendered output. The abstract must render as full-width and the introduction onward as two columns. If the abstract wraps into two narrow columns, the first section break landed before the abstract — move it.\n\n**Section index bookkeeping.** Each `add /body --type section` inserts one empty paragraph into `/body` (the section-break marker). All subsequent `p[N]` indices shift by +1 per section break. Plan section breaks in advance; after adding a break, `officecli get \"$FILE\" /body --depth 1` to re-index before continuing.\n\nFull section schema (`columns`, `columnSpace`, `orientation`, `pageNumFmt`, `titlePage`, `lineNumbers`): `officecli help docx section`.\n\n## Abstract / keywords / affiliation block\n\nFirst-page metadata stack: title (centered 20-22pt bold) → authors (centered 12pt, superscript `^1 ^2` for multi-affiliation) → affiliations (centered 11pt, keyed to superscripts) → submission target / date → **Abstract** heading (14pt bold) → abstract body (block-style, **NO `firstLineIndent`**, 150-300 words) → keywords line (italic 11pt). Same \"cover ≥ 60% filled\" rule as docx v2.\n\n```bash\n# Superscript affiliation markers (multi-institution paper)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen\" --prop align=center --prop size=12pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\"1\" --prop superscript=true\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\", Bob Martinez\"\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\"2\" --prop superscript=true\n# Running header (skip on cover via type=first empty header — see docx v2 §headers)\nofficecli add \"$FILE\" / --type header --prop type=default --prop align=right --prop size=9pt --prop text=\"Short Running Title\"\n```\n\n**Nature-family 2-col abstract** is rare — if required, open a `section type=continuous columns=2` BEFORE the abstract heading; short abstracts (<100 words) leave ragged columns. **Mirrored odd/even headers** are exposed by the high-level API: `officecli set \"$FILE\" /settings --prop evenAndOddHeaders=true`, then add the even header with `--type header --prop type=even`. Full header schema: `officecli help docx header`.\n\n## QA — Delivery Gate (executable)\n\n**Assume there are problems. Your job is to find them.** First render is almost never correct. Run this block before declaring done.\n\n### Gates 1-3 — inherited from docx v2\n\n→ see docx v2 §Delivery Gate. Schema validate, token leak grep, live PAGE field structure. Copy-paste the docx v2 gate block first. Every check must print its success message.\n\n### Gate 4 — citation round-trip\n\nEvery in-text citation key should resolve to a bibliography entry. Count mismatches = REJECT.\n\n```bash\n# IEEE example (bracketed numerics). Adjust regex for APA (Author, Year) or MLA (Author Page).\nCITATIONS=$(officecli view \"$FILE\" text | grep -oE '\\[[0-9]+\\]' | sort -u | wc -l)\nENTRIES=$(officecli query \"$FILE\" 'paragraph[hangingIndent]' --json | jq '.data.results | length')\necho \"In-text citation markers: $CITATIONS | Bibliography entries: $ENTRIES\"\n# REJECT when citations exceed entries (cites without references). Entries > citations is allowed by some venues.\n[ \"$CITATIONS\" -le \"$ENTRIES\" ] && echo \"Gate 4 OK\" || { echo \"REJECT Gate 4: $CITATIONS in-text markers but only $ENTRIES bibliography entries\"; exit 1; }\n```\n\n### Gate 5a — SEQ presence + cached numbers distinct\n\nIf the paper has any numbered figure or table, the body must carry live `SEQ` fields AND their cached values must show distinct ascending numbers (else `view text` and downstream viewers that don't recompute cached fields will show \"Figure 1\" for all).\n\n```bash\n# Count SEQ fields via query (raw-grep collapses multi-matches on one XML line → undercounts).\nSEQ_COUNT=$(officecli query \"$FILE\" 'field[fieldType=seq]' --json | jq '.data.results | length')\nVISIBLE_FIG=$(officecli view \"$FILE\" text | grep -cE '(Figure|Table) [0-9]+')\nif [ \"$VISIBLE_FIG\" -gt 0 ] && [ \"$SEQ_COUNT\" -eq 0 ]; then\n  echo \"REJECT Gate 5a: $VISIBLE_FIG visible Figure/Table labels but 0 SEQ fields.\"\n  exit 1\nfi\n# Cached values must be distinct. Run `set / --prop recalcFields=seq` once after all captions exist;\n# before recalc the fields render the #OCLI_NOTEVAL! sentinel, after recalc Figure 1 / Figure 2 / Figure 3:\nDISTINCT=$(officecli view \"$FILE\" text | grep -oE '(Figure|Table) [0-9]+' | sort -u | wc -l)\n[ \"$SEQ_COUNT\" -le \"$DISTINCT\" ] && echo \"Gate 5a OK (SEQ=$SEQ_COUNT, distinct=$DISTINCT)\" || { echo \"REJECT Gate 5a: $SEQ_COUNT SEQ fields but only $DISTINCT distinct rendered labels — run 'set \\\"$FILE\\\" / --prop recalcFields=seq'\"; exit 1; }\n```\n\n### Gate 5b — Visual audit via HTML preview (MANDATORY, not optional)\n\nGates 1–5a catch schema, token leaks, live-field presence, citation counts. **They do NOT catch physical assembly defects** — scrambled page order, a duplicated Abstract mid-document, three figures all labeled \"Fig. 1\" despite SEQ field presence, equation variables rendering as plain-text LaTeX (`lambda_1`, `x_{t+1}`) instead of math. Do not skip — Gates 1–5a pass ≠ visual OK.\n\nRun `officecli view \"$FILE\" html` and Read the returned HTML path. For every page of the paper, answer:\n\n> (a) Are pages in logical academic sequence? (Title → Abstract → Keywords → Introduction → body → References — no forward jumps, no backward leaks.)\n> (b) Does the Abstract appear exactly once, not duplicated mid-document?\n> (c) Are Figure N / Table N labels distinct and ascending? (Fig. 1, Fig. 2, Fig. 3 — not all \"Fig. 1\". Same for tables.)\n> (d) Do equations render as math? (Italicized variables, Greek letters like λ / α, proper integrals / fractions — NOT plain-text `lambda_1`, `x_{t+1}`, `\\int`.)\n> (e) For IEEE papers: are section titles ALL CAPS with Roman numerals (`I. INTRODUCTION`)? Are tables Roman (`Table I`, `Table II`)?\n> (f) For APA papers: are Level-1 headings centered bold and unnumbered (not `1. Introduction`)?\n> (g) Does every in-text \"see Fig. N\" / \"see Table N\" resolve to a figure/table that actually carries that number?\n> (h) Heading hierarchy visually distinct (size + weight) across H1 / H2 / H3?\n\nReport every instance. If even one defect is present → REJECT; do not deliver until fixed.\n\n**Human preview (optional).** If you want the user to visually preview the paper, run `officecli watch \"$FILE\"` for a live preview the user can open at their own discretion, or have them open the `.docx` directly in Word / WPS / Pages. For final visual verification, open the file in the target viewer.\n\n### Honest limit\n\n`validate` catches schema errors, not academic-style errors. A document passes `validate` with APA citations in an IEEE paper, footnotes in a style that forbids them, or figures with hardcoded numbers that drift when a new figure is inserted. The gates above — especially Gate 4 (round-trip) and Gate 5 (SEQ presence) — are how you catch what validate cannot.\n\n## Known Issues & Pitfalls (academic-specific)\n\n→ Base pitfalls (shell escape, `\\$ \\t \\n` literals, table cell formatting order, page-break boundaries, `shd.fill` / `ind.firstLine` schema-invalid forms, TOC cached values, watermark two-step): see docx v2 §Known Issues & Pitfalls.\n\nAcademic-specific:\n\n- **`\\left(...\\right)` / `\\left[...\\right]` with a sub/superscript INSIDE the delimiters → parse error** (`cast object … Subscript`). An outer script (`\\left(x+y\\right)^2`) is fine. Use plain `(`, `)`, `[`, `]` — OMML auto-sizes in display mode.\n- **`move` on `/body/oMathPara[N]` reorders the equation** (the wrapping paragraph moves). `--before <path>` may leave a stray empty paragraph; prefer `--index` / `--after`.\n- **Section break +1 paragraph offset.** Each `add /body --type section` inserts one empty paragraph into `/body`. All `p[N]` indices after the break shift by +1. Plan breaks; after any `add section`, `officecli get \"$FILE\" /body --depth 1` to re-index.\n- **`/section[last()]` resolves to the final section** (mirrors `p[last()]`), for both get and set. An explicit `/section[N]` also works:\n  ```bash\n  SECTION_COUNT=$(officecli query \"$FILE\" section --json | jq '.data.results | length')\n  # use /section[last()] for the final section, or an explicit /section[2], /section[3], ...\n  ```\n  Each `add /body --type section` increments the count. Re-query after every break.\n- **Multi-column does NOT auto-revert.** After a `columns=2` section, you must add another section break and explicitly set `columns=1` on the new `/section[N]` (N = post-revert count) — otherwise the rest of the document, including references, renders as two columns. Verify with `officecli get \"$FILE\" \"/section[N]\"` for each N.\n- **`--type equation` on a `tc[N]` cell path is rejected with a guard error** (`table cells only accept paragraphs, tables, or SDTs`) — no bad XML is written. Inside a table cell, target `tc[N]/p[1]` with `--prop mode=inline` instead.\n- **SEQ caption numbers are filled by `set / --prop recalcFields=seq`** (run once after all captions exist), not by per-field raw-set patching. A pre-recalc SEQ field shows the `#OCLI_NOTEVAL!` sentinel in `view text`.\n- **Hanging-indent canonical form is `indent=720 hangingIndent=720`.** Not `ind.firstLine=-720`. The dotted form emits `<w:ind>` after `<w:jc>` and fails schema on emit.\n- **Footnote reference runs show as empty strings in `view annotated`.** The `<w:footnoteReference>` XML element has no visible text on the reference side; the note body lives in `/footnotes/footnote[N]`. Confirm with `officecli query \"$FILE\" 'footnote'`, not by eyeballing `view text`.\n- **Caption placement:** Table caption ABOVE the table; Figure caption BELOW the figure. Every major style (APA, Chicago, IEEE, MLA) agrees. Putting a Table caption below the table is an academic-style error, not a rendering issue — `validate` will not catch it.\n- **TOC cached rendering / shell-escape:** → see docx v2 §Table of Contents, §Shell escape.\n\n## Renderer quirks (cross-viewer)\n\n→ see docx v2 §Renderer quirks. PAGE / TOC cached values, OMML baseline shifts, scheme colors — all identical quirks apply to academic papers. Before calling an equation or a citation marker broken, open the file in the user's target viewer (Word, WPS, Pages) — if it renders correctly there, it is a viewer quirk, not a skill defect.\n\n## Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <element> --json`. Help is the authoritative schema; this skill is the decision guide for academic deltas on top of docx v2.\n","skills/morph-ppt-3d/SKILL.md":"---\nname: morph-ppt-3d\ndescription: 3D Morph PPT — extends morph-ppt with GLB model insertion, cinematographic camera, model-content layout, and enriched visual design system.\n---\n\n# Morph PPT — 3D Extension\n\nThis skill **extends** `morph-ppt`. All morph-ppt rules (naming, ghosting, design, verification) apply in full.\nThis file covers **3D-specific additions** and an **enriched design system** combining morph-ppt aesthetics with concrete color palettes, font pairings, and layout quality guardrails.\n\n---\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Use when\n\n- User wants a `.pptx` with a `.glb` 3D model and Morph transitions.\n\n---\n\n## 3D Model Compatibility Gate (before generation)\n\n1. Only `.glb` is supported. If user provides `.fbx` / `.obj` / `.blend` / `.usdz` / `.gltf`, ask them to convert to `.glb` first (e.g. via Blender export).\n2. If user has no model, follow the **Model Discovery Flow** below.\n3. All files (`.glb`, `.pptx`, build script) must be in the same working directory.\n\n---\n\n## Model Discovery Flow (when user has no model)\n\nWhen the user gives a topic but no `.glb` file, **proactively help them find a matching model** instead of just listing websites.\n\n### Step 1: Understand the topic and suggest model direction\n\nBased on the user's topic, suggest what kind of 3D model would work:\n\n| Topic type         | Model suggestion                    | Example                                               |\n| ------------------ | ----------------------------------- | ----------------------------------------------------- |\n| Product/brand      | The actual product or a similar one | \"coffee brand\" → coffee cup, coffee machine, bean     |\n| Animal/character   | The animal or mascot                | \"fox mascot\" → fox 3D model                           |\n| Architecture/space | Building, room, or structure        | \"new office\" → office building, interior              |\n| Vehicle/transport  | The vehicle itself                  | \"EV launch\" → car, motorcycle, bicycle                |\n| Food/cooking       | The dish or ingredient              | \"Japanese food\" → sushi platter, ramen bowl           |\n| Tech/gadget        | The device                          | \"phone launch\" → phone, tablet, laptop                |\n| Nature/science     | The subject                         | \"solar system\" → planet, sun, earth                   |\n| Abstract concept   | A symbolic object                   | \"teamwork\" → puzzle pieces, gears, bridge             |\n\nTell the user: \"Your topic is [X]. I suggest using a 3D model of [description]. Here are some free sources to find one:\"\n\n### Step 2: Search for models (agent-driven)\n\n**Proactively search for models on behalf of the user.** Don't just list websites — actually find candidates.\n\n**Search strategy (try in order):**\n\n1. **Web search** for free GLB models matching the topic:\n\n   ```\n   Search: \"[topic keyword] 3d model glb free download\"\n   Example: \"fox 3d model glb free download\"\n   ```\n\n2. **Sketchfab API** (no auth needed for search):\n\n   ```bash\n   curl -s \"https://api.sketchfab.com/v3/search?type=models&q=[keyword]&downloadable=true&archives_flavours=glb\" \\\n     | python3 -c \"\n   import json, sys\n   data = json.load(sys.stdin)\n   for m in data.get('results', [])[:5]:\n       print(f\\\"Name: {m['name']}\\\")\n       print(f\\\"URL: https://sketchfab.com/3d-models/{m['slug']}-{m['uid']}\\\")\n       print(f\\\"Likes: {m.get('likeCount', 0)}, License: {m.get('license', {}).get('label', 'unknown')}\\\")\n       print()\n   \"\n   ```\n\n3. **Poly Pizza** (direct GLB download, all free):\n\n   ```bash\n   # Search results page — parse for download links\n   curl -s \"https://poly.pizza/api/search/[keyword]\" 2>/dev/null\n   ```\n\n4. **Khronos glTF-Sample-Assets** (guaranteed to work, always available):\n   ```bash\n   # Direct download — no auth, no API, always works\n   curl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/[ModelName]/glTF-Binary/[ModelName].glb\"\n   ```\n   Available models: Duck, Fox, Avocado, BrainStem, CesiumMan, DamagedHelmet, FlightHelmet, Lantern, Suzanne, WaterBottle, etc.\n\n### Step 3: Present candidates to user for confirmation\n\nShow the user 2-3 model options with:\n\n- Model name and source\n- Preview link (Sketchfab URL or description)\n- License info\n- Why this model fits their topic\n\nExample response:\n\n```\nBased on your topic \"fox mascot\", here are some models I found:\n\n1. Fox (Khronos sample)\n   Direct download, guaranteed compatible\n   Why: clean fox model, good for mascot/character decks\n\n2. Low Poly Fox (Poly Pizza)\n   URL: https://poly.pizza/m/xxx\n   License: CC0 (completely free)\n   Why: low-poly style, good fit for clean minimal design\n\n3. Cartoon Fox (Sketchfab)\n   URL: https://sketchfab.com/3d-models/fox-xxx\n   License: CC BY 4.0 (free, commercial use ok)\n   Why: expressive face, high detail\n\nWhich one do you want? I'll download it and start building.\n```\n\n**Wait for user confirmation before downloading.** Do not download without asking.\n\n### Step 4: Download the confirmed model\n\nAfter user confirms, download directly:\n\n```bash\n# For Sketchfab (if user has the download URL)\ncurl -L -o model.glb \"[download_url]\"\n\n# For Khronos samples (always works)\ncurl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Fox/glTF-Binary/Fox.glb\"\n```\n\nAfter download, verify:\n\n- File exists and is not empty\n- File extension is `.glb`\n- File size is under 50MB\n\nIf Sketchfab requires login to download, tell the user:\n\n> \"This model requires a Sketchfab login to download. You can grab the .glb file from the page and share it with me. Or I can use a Khronos sample model for a demo version first?\"\n\n### Step 5: When user says \"anything\" / \"you decide\" / \"just make a demo\"\n\n**Don't just grab a random model.** First guide the user to clarify their PPT topic:\n\n> Sure! I'll handle the model — but let me confirm the topic direction first so the model matches the content:\n>\n> 1. Tech/Product — headphones, phone, robot...\n> 2. Animal/Character — cute pet, cartoon character...\n> 3. Architecture/Space — building, interior, city...\n> 4. Food/Lifestyle — dishes, everyday objects...\n> 5. Other — just tell me your idea\n>\n> Pick a direction, or just give me a topic keyword.\n\nAfter user confirms a direction, THEN search and recommend models.\n\n### Step 6: When user wants to find models themselves\n\nGive specific website links with step-by-step guidance:\n\n> **Recommended 3D model websites:**\n>\n> 1. **Sketchfab** (largest 3D model platform)\n>    - Link: https://sketchfab.com/search?q=[keyword]&type=models&downloadable=true\n>    - Filter steps: search keyword → check \"Downloadable\" → format \"glTF\" → sort by \"Likes\"\n>    - When downloading, select **glTF (.glb)** format\n>    - Note: some models require free registration to download\n> 2. **Poly Pizza** (all free low-poly)\n>    - Link: https://poly.pizza/\n>    - All CC0 licensed — click Download to get .glb directly\n>    - Best for: minimalist or cartoon-style presentations\n> 3. **Sketchfab popular categories**\n>    - Animals: https://sketchfab.com/search?q=animal&type=models&downloadable=true\n>    - Food: https://sketchfab.com/search?q=food&type=models&downloadable=true\n>    - Tech: https://sketchfab.com/search?q=gadget&type=models&downloadable=true\n>    - Architecture: https://sketchfab.com/search?q=architecture&type=models&downloadable=true\n> 4. **Free3D** (general free model site)\n>    - Link: https://free3d.com/3d-models/glb\n>    - Note: check the license type before use\n> 5. **TurboSquid Free** (pro model site free section)\n>    - Link: https://www.turbosquid.com/Search/3D-Models/free/glb\n>\n> After downloading, share the .glb file with me. If the download is a .gltf folder, use Blender to convert it to .glb.\n\n### Step 7: When user gives keywords and asks agent to search\n\n**Remind about token cost before searching:**\n\n> I can search for you, but web searches use extra tokens. Would you prefer:\n>\n> A. I search — I use the Sketchfab API and recommend 2-3 options (uses a few tokens)\n> B. Self-service — I give you search links and filter steps, you pick and share with me (no extra tokens)\n>\n> A or B?\n\nIf user chooses A, proceed with Step 2 (agent-driven search).\nIf user chooses B, proceed with Step 6 (self-service guidance).\n\n### License reminder\n\nAlways remind before confirming download: \"Please check the model license before downloading. CC0 / CC BY = free to use; CC BY-NC = non-commercial only.\"\n\n---\n\n## Visual Design System (4.0 enrichment)\n\nmorph-ppt provides the base design rules. This section adds **concrete palettes, font pairings, and layout quality rules** from PPT Creator to give the AI more variety and stronger guardrails.\n\n### Color Palettes (pick one per deck, or blend)\n\nChoose a palette that matches the **topic mood** — don't default to generic blue.\n\n| Palette                | Primary               | Secondary             | Accent           | Body Text | Muted/Caption |\n| ---------------------- | --------------------- | --------------------- | ---------------- | --------- | ------------- |\n| **Coral Energy**       | `F96167` (coral)      | `F9E795` (gold)       | `2F3C7E` (navy)  | `333333`  | `8B7E6A`      |\n| **Midnight Executive** | `1E2761` (navy)       | `CADCFC` (ice blue)   | `FFFFFF`         | `333333`  | `8899BB`      |\n| **Forest & Moss**      | `2C5F2D` (forest)     | `97BC62` (moss)       | `F5F5F5` (cream) | `2D2D2D`  | `6B8E6B`      |\n| **Charcoal Minimal**   | `36454F` (charcoal)   | `F2F2F2` (off-white)  | `212121`         | `333333`  | `7A8A94`      |\n| **Warm Terracotta**    | `B85042` (terracotta) | `E7E8D1` (sand)       | `A7BEAE` (sage)  | `3D2B2B`  | `8C7B75`      |\n| **Berry & Cream**      | `6D2E46` (berry)      | `A26769` (dusty rose) | `ECE2D0` (cream) | `3D2233`  | `8C6B7A`      |\n| **Ocean Gradient**     | `065A82` (deep blue)  | `1C7293` (teal)       | `21295C`         | `2B3A4E`  | `6B8FAA`      |\n| **Teal Trust**         | `028090` (teal)       | `00A896` (seafoam)    | `02C39A` (mint)  | `2D3B3B`  | `5E8C8C`      |\n| **Sage Calm**          | `84B59F` (sage)       | `69A297` (eucalyptus) | `50808E`         | `2D3D35`  | `7A9488`      |\n| **Cherry Bold**        | `990011` (cherry)     | `FCF6F5` (off-white)  | `2F3C7E` (navy)  | `333333`  | `8B6B6B`      |\n\n**Rules:**\n\n- One color dominates (60-70% visual weight), 1-2 supporting tones, one accent\n- On light backgrounds: use Body Text color for copy, Muted for captions\n- On dark backgrounds: use Secondary or `FFFFFF` for copy, Muted for captions\n- For additional inspiration, browse `../morph-ppt/reference/styles/INDEX.md` — 50+ visual styles organized by mood (dark, light, warm, vivid, bw). Read `style.md` for design philosophy, `build.sh` for implementation reference. **Learn the approach, do not copy coordinates verbatim**\n\n### Font Pairings (pick one per deck)\n\n| Header Font  | Body Font     | Best For                         |\n| ------------ | ------------- | -------------------------------- |\n| Georgia      | Calibri       | Formal business, finance         |\n| Arial Black  | Arial         | Bold marketing, product launches |\n| Calibri      | Calibri Light | Clean corporate, minimal         |\n| Cambria      | Calibri       | Traditional professional         |\n| Trebuchet MS | Calibri       | Friendly tech, startups          |\n| Impact       | Arial         | Bold headlines, keynotes         |\n| Palatino     | Garamond      | Elegant editorial, luxury        |\n| Consolas     | Calibri       | Developer tools, technical       |\n\n### Hard Rules (mandatory, no exceptions)\n\n**H4 — Body text minimum 16pt:**\nAll body text, card content, and bullet points must be >= 16pt. \"Content doesn't fit\" is not an excuse — reduce text, split slides, or reduce card count instead. Exceptions: chart axis labels (<=12pt), short sublabels (<=14pt, max 5 words), footnotes.\n\n**H6 — Dark background contrast:**\nWhen slide background brightness < 30% (e.g. `1E2761`, `36454F`, `000000`), ALL body text, card content, chart labels, and icon fills MUST use white (`FFFFFF`) or near-white (brightness > 80%). Never use mid-gray or muted colors as body text on dark backgrounds.\n\n**H7 — Speaker notes required:**\nEvery content slide (not title/closing) MUST have speaker notes. Use:\n\n```bash\nofficecli add deck.pptx '/slide[N]' --type notes --prop text=\"...\"\n```\n\n### Visual Element Checkpoint\n\n**Every 3 content slides, at least 1 must contain a non-text visual element:**\n\n| Visual type            | Implementation                               |\n| ---------------------- | -------------------------------------------- |\n| Icon in colored circle | ellipse shape + centered text/number overlay |\n| Colored block          | `preset=roundRect` with fill                 |\n| Large stat number      | `size=64, bold=true` with small label below  |\n| Chart                  | `--type chart` (column/pie/line)             |\n| Gradient background    | `background=COLOR1-COLOR2-180`               |\n| Shape composition      | circles + connectors for diagrams            |\n\nText-only slides are only allowed for: quotes, code examples, pure tables.\n\n---\n\n## 3D Model Insertion Rules\n\n### Add model fresh on every slide — NEVER clone\n\n`morph_clone_slide` copies the model as frozen XML. The cloned model cannot Morph.\nEach slide must call `add --type 3dmodel` independently with the **same `name`** prop.\n\n**⚠️ CRITICAL: If you clone a slide that already has a 3D model, the old model XML is copied too. This creates TWO model3d elements with the same name on the new slide. PowerPoint cannot handle this conflict and will delete the model content during repair.**\n\nIf you must clone a slide for scene actors, **immediately remove the cloned model before adding a new one:**\n\n```bash\n# After cloning slide 1 to slide 2:\nofficecli remove deck.pptx '/slide[2]/model3d[1]'  # remove the frozen clone\nofficecli add deck.pptx '/slide[2]' --type 3dmodel ...  # add fresh model\n```\n\n**Recommended approach: Do NOT clone slides with 3D models at all.** Create all slides empty first, then add models fresh on each.\n\n```bash\n# Slide 1\nofficecli add deck.pptx '/slide[1]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=16cm --prop y=1cm --prop width=16cm --prop height=16cm \\\n  --prop roty=0\n\n# Slide 2\nofficecli add deck.pptx '/slide[2]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=0.5cm --prop y=1cm --prop width=18cm --prop height=17cm \\\n  --prop roty=50\n```\n\n### Controllable properties\n\n| Property          | What it does              | Notes                                         |\n| ----------------- | ------------------------- | --------------------------------------------- |\n| `x`, `y`          | Position on slide         | Standard slide coordinates                    |\n| `width`, `height` | Frame size                | Model renders inside this frame               |\n| `name`            | Shape name                | Must be identical across slides for Morph     |\n| `roty`            | Y-axis rotation (degrees) | Primary storytelling axis                     |\n| `rotx`            | X-axis tilt (degrees)     | Range -25 to +40. See Camera Language section |\n| `rotz`            | Z-axis roll (degrees)     | Rarely needed                                 |\n\n### Do NOT manually set\n\n- `meterPerModelUnit` — auto-computed from GLB bounding box\n- `preTrans` — auto-computed for model centering\n- `camera` depth/position — auto-computed to fit the model\n- Never use `raw-set` on any 3D transform parameter\n\n---\n\n## Model-Content Layout\n\n### Core Principle: Model IS the Subject\n\nThe model must feel like the **protagonist** of the presentation, not a sidebar decoration.\nText supports the model; the model does not decorate the text.\n\n### Size Contrast Rule (MANDATORY)\n\nAdjacent slides must have a model area ratio >= 1.5x or <= 0.67x.\nCompute area as `width × height`. If slide N model is 16×15=240 cm², slide N+1 must be >= 360 or <= 160.\n\n**Never use similar sizes on consecutive slides.** This is the single most important rule for visual energy.\n\n| Size tier      | Width   | Height  | Area (approx) | When to use                                |\n| -------------- | ------- | ------- | ------------- | ------------------------------------------ |\n| **XL (bleed)** | 28-36cm | 22-28cm | 600-1000      | Close-up, model extends beyond slide edges |\n| **L (hero)**   | 18-24cm | 15-19cm | 270-456       | Title, closing, dramatic moments           |\n| **M (split)**  | 13-17cm | 12-16cm | 156-272       | Standard content pages with text           |\n| **S (accent)** | 5-10cm  | 5-10cm  | 25-100        | Data-heavy pages, model as icon            |\n\n### Layout Patterns (6 types)\n\n**A — Model right, content left** (content pages)\nContent at x=1-14cm. Model at x=15-20cm, width 14-18cm.\n\n**B — Model left, content right** (alternate with A)\nModel at x=0-2cm, width 14-18cm. Content at x=18-32cm.\n\n**C — Model centered, text overlay** (title/closing)\nModel centered large (18-24cm). Text at slide top or bottom.\n\n**D — Model small corner, content dominant** (data pages)\nModel 5-10cm in any corner. Content fills the rest.\n\n**E — Model as backdrop** (impact/quote pages)\nModel XL (28-36cm), centered, partially cropped by slide edges.\nText overlaid directly on top of model area with high-contrast color.\nThe model becomes the \"canvas\" — text lives inside the model's space.\n\n```bash\n# Pattern E: model fills slide as backdrop\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=-2cm --prop y=-2cm --prop width=38cm --prop height=24cm \\\n  --prop roty=45 --prop rotx=10\n\n# Text overlaid on model\nofficecli add deck.pptx '/slide[N]' --type shape \\\n  --prop 'name=#sN-quote' --prop text=\"Key insight here\" \\\n  --prop x=3cm --prop y=7cm --prop width=28cm --prop height=5cm \\\n  --prop size=44 --prop bold=true --prop color=FFFFFF --prop fill=none\n```\n\n**F — Model bleed edge** (transition/teaser pages)\nModel partially off-screen (negative x or y, or x+width > 33.87cm).\nOnly part of the model visible — implies more beyond the frame.\n\n```bash\n# Pattern F: model bleeds off right edge\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=20cm --prop y=-1cm --prop width=24cm --prop height=22cm \\\n  --prop roty=70\n```\n\n### Layout Progression\n\nNever repeat the same pattern on consecutive slides. Example:\n\n```\nSlide 1: C (centered hero, L)\nSlide 2: E (backdrop close-up, XL)   ← 1.5x+ area jump\nSlide 3: A (model right, M)          ← pull back\nSlide 4: F (bleed edge, L)           ← push in\nSlide 5: D (small corner, S)         ← dramatic pull back\nSlide 6: B (model left, M)           ← grow\nSlide 7: C (centered closing, L)     ← push in\n```\n\n### Text Layout Safety (MANDATORY)\n\n**Text boxes must never overlap each other or the model frame.**\n\nRules:\n\n1. **Title and body must not collide.** If a title wraps to 2 lines, the body `y` must account for the title's actual height, not the planned height. Safe formula: `body_y = title_y + title_height + 0.5cm`\n2. **Fixed-height text boxes are dangerous.** If text content is longer than expected, it will overflow invisibly. Use generous heights: title `3-4cm`, body `6-8cm`, bullets `8-10cm`.\n3. **Model frame and text boxes: gap >= 1cm.** Calculate: if model is at `x=15cm`, text `x + width` must be <= `14cm`.\n4. **On Pattern C (centered model + text overlay):** text goes at slide top (`y=0.5-2cm`) or bottom (`y=14-17cm`), NOT in the vertical middle where the model lives (`y=3-13cm`).\n5. **After building each slide, verify coordinates:**\n   ```bash\n   officecli get deck.pptx '/slide[N]' --depth 1\n   # Check: no two shapes share overlapping x/y/width/height ranges\n   ```\n\n### Model Bleed Guidelines\n\n**Not every model looks good when cropped.** Bleed (Pattern E/F) works best for:\n\n- ✅ Symmetric objects (spheres, helmets, bottles) — any crop looks intentional\n- ✅ Large flat surfaces (cars, buildings) — partial view implies scale\n- ✅ When cropping non-critical parts (background, base, stand)\n\nBleed does NOT work for:\n\n- ❌ Character/animal models — cropping ears, tails, or limbs looks broken\n- ❌ Small detailed models — cropping loses the detail you want to show\n- ❌ When the cropped part is the most recognizable feature\n\n**For character/animal models (like fox, duck, avocado):** keep the full model visible on all slides. Use size changes (L→M→S) for rhythm instead of bleed cropping. Use `rotx` for angle variety instead.\n\n---\n\n## Camera Language\n\nThree tools work together: **roty** (orbit), **rotx** (tilt), **width/height** (zoom).\n\n### Shot Types (use >= 3 different per deck)\n\n| Shot                     | Size                  | rotx       | When                        |\n| ------------------------ | --------------------- | ---------- | --------------------------- |\n| **Establishing**         | L (18-24cm)           | 0-5        | Title, intro, closing       |\n| **Three-quarter beauty** | L (16-20cm)           | 5-10       | Hero, first impression      |\n| **Close-up**             | XL (28-36cm), cropped | 0-10       | Feature highlight, detail   |\n| **Bird's eye**           | M (13-17cm)           | 25-40      | Structure, overview         |\n| **Low angle**            | L (16-20cm)           | -15 to -25 | Power, drama                |\n| **Side profile**         | M (13-16cm)           | 0          | Form factor, silhouette     |\n| **Over-the-shoulder**    | S (5-10cm)            | 10-15      | Data-heavy, model as accent |\n\n### Content-Driven Camera\n\nMatch the shot to what the slide talks about:\n\n- \"Front design\" → Close-up, `roty=0`, XL cropped\n- \"Side profile\" → Side, `roty=90`, M\n- \"Internal structure\" → Bird's eye, `roty=30, rotx=35`, M\n- \"Power/authority\" → Low angle, `roty=20, rotx=-20`, L\n- \"Data & specs\" → Over-the-shoulder, `roty=60`, S in corner\n\n### Rotation Rules\n\n1. Adjacent roty delta: 30-90° (< 30 = jitter, > 90 = disorienting)\n2. Overall roty direction must be consistent (no back-and-forth)\n3. rotx range: -25 to +40. Adjacent rotx delta <= 20\n4. Total arc across deck: 180-360° (show the model from all sides)\n\n### Example Shot Plan\n\n| Slide | Shot                 | roty | rotx | Size     | Pattern |\n| ----- | -------------------- | ---- | ---- | -------- | ------- |\n| 1     | Three-quarter beauty | 30   | 8    | L 20×17  | C       |\n| 2     | Close-up             | 0    | 5    | XL 30×24 | E       |\n| 3     | Side profile         | 80   | 0    | M 15×14  | A       |\n| 4     | Bird's eye           | 120  | 35   | M 14×13  | B       |\n| 5     | Low angle            | 170  | -20  | L 20×18  | F       |\n| 6     | Over-the-shoulder    | 220  | 10   | S 8×7    | D       |\n| 7     | Establishing         | 320  | 5    | L 20×17  | C       |\n\n---\n\n## Workflow Integration with morph-ppt\n\n### Phase 2 additions (Planning)\n\nIn `brief.md`, add a **Model Choreography Table**:\n\n| Slide | Pattern | Size Tier | Model x,y,w,h | roty | rotx |\n| ----- | ------- | --------- | ------------- | ---- | ---- |\n| 1     | C       | L         | 7,0.5,20,17   | 30   | 8    |\n| 2     | E       | XL        | -2,-2,38,24   | 0    | 5    |\n| ...   | ...     | ...       | ...           | ...  | ...  |\n\nVerify the area ratio rule (>= 1.5x between adjacent rows) before proceeding to build.\n\n### Phase 3 additions (Build)\n\nSince models cannot be cloned, the build script differs from standard morph-ppt:\n\n1. Create all slides first (with background + morph transition)\n2. Add scene actors (`!!scene-*`) on slide 1, then clone slides for morph continuity\n3. Add 3D model fresh on EACH slide (same name, different roty/position)\n4. Add content shapes per slide, ghost previous content\n\n```python\nmodel_positions = [\n    {\"slide\": 1, \"x\": \"7cm\",  \"y\": \"0.5cm\", \"w\": \"20cm\", \"h\": \"17cm\", \"roty\": 30},\n    {\"slide\": 2, \"x\": \"-2cm\", \"y\": \"-2cm\",  \"w\": \"38cm\", \"h\": \"24cm\", \"roty\": 0},\n    {\"slide\": 3, \"x\": \"16cm\", \"y\": \"1cm\",   \"w\": \"15cm\", \"h\": \"14cm\", \"roty\": 80},\n    # ...\n]\nfor pos in model_positions:\n    run(\"officecli\", \"add\", OUTPUT, f\"/slide[{pos['slide']}]\", \"--type\", \"3dmodel\",\n        \"--prop\", f\"path={MODEL}\", \"--prop\", \"name=!!model-hero\",\n        \"--prop\", f\"x={pos['x']}\", \"--prop\", f\"y={pos['y']}\",\n        \"--prop\", f\"width={pos['w']}\", \"--prop\", f\"height={pos['h']}\",\n        \"--prop\", f\"roty={pos['roty']}\")\n```\n\n### Phase 4 additions (Verification)\n\nAfter standard morph verification, additionally check:\n\n- Each slide has exactly one `model3d` element\n- All models share the same `name` prop\n- Adjacent slides have model area ratio >= 1.5x or <= 0.67x\n- No two consecutive slides use the same layout pattern\n\n---\n\n## File Placement Rule\n\nAll files must be in the same working directory.\n\n**Deliverables (exactly 4 files, no more):**\n\n- `.glb` model file (the 3D model used in the deck)\n- Output `.pptx`\n- Build script (re-runnable)\n- `brief.md`\n\n**Do NOT create additional files** such as outline.md, quality-report.md, test-report.md, etc. All planning goes in `brief.md`, all verification output goes to stdout. Extra files confuse users.\n\nDo not scatter model files across unrelated paths.\n","skills/morph-ppt/SKILL.md":"---\nname: morph-ppt\ndescription: \"Use this skill when the user wants a .pptx with smooth cross-slide animation — PowerPoint Morph transitions, Keynote-style continuous motion, shapes that grow / move / rotate as the slide advances. Trigger on: 'morph', 'morph transition', 'smooth transition', 'continuous animation across slides', 'Keynote-style transition', 'animated slide sequence', 'shape continuity across slides'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate 1–5a). DO NOT invoke for a generic deck, pitch deck, or board review without cross-slide motion — route those to officecli-pptx base or officecli-pitch-deck.\"\n---\n\n# OfficeCLI Morph-PPT Skill\n\n**This skill is a scene layer on top of `officecli-pptx`.** Every pptx hard rule — visual delivery floor (title ≥ 36pt / body ≥ 18pt / title ≥ 2× body), 12-column grid on 33.87×19.05cm, canonical palettes, chart-choice decision table, connector canon, shell escape, resident + batch, Delivery Gate 1–5a — is inherited, not re-taught. This file adds only what **Morph** needs on top: cross-slide shape-name binding, Scene Actors vs content prefixing, ghost discipline, `transition=morph` CLI quirks, 52-style visual library lookup, and a morph-specific fresh-eyes Gate 5b extension.\n\nWhen the pptx base rules cover it, the text here says `→ see pptx v2 §X`. Read `skills/officecli-pptx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches the Morph workflow — when shape names must match, when to ghost, when the CLI auto-prefixes — not every command flag.** When a prop name, enum, or preset is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help pptx slide           # authoritative for: transition, advanceTime, advanceClick, background\nofficecli help pptx transition      # transition / transitionDuration / transitionSpeed (Parent: slide)\nofficecli help pptx shape           # name, preset, x/y/width/height, fill, rotation, opacity, animation\nofficecli help pptx animation       # preset + trigger + duration values\nofficecli help pptx <element> --json  # machine-readable schema\n```\n\nHelp reflects the installed CLI version. When skill and help disagree, **help wins.** Every `--prop X=` in this file is grep-verified against `officecli help pptx <element>`. Specific confirmations: `transition=morph` is a listed value on `slide`; `advanceTime` / `advanceClick` are valid. `transition` is a real element (`officecli help pptx transition`, Parent: slide, set/get) — it exposes `transition`, `transitionDuration`, and `transitionSpeed`. Set the transition with the high-level path `set <slide> --prop transition=morph`; tune speed/duration with the combined shorthand `transition=morph-slow` (or `-fast`, or `transition=morph-<DUR_MS>`). Speed/duration are set only via that shorthand on the `transition` prop, not as independent sub-props. Both round-trip on readback: `transition=morph-slow`/`-fast` reads back as `transitionSpeed=slow`/`fast`, and `transition=morph-<DUR_MS>` (e.g. `morph-1500`) reads back as `transitionDuration=1500`.\n\n## Mental Model & Inheritance\n\n**Inherits pptx v2.** You should have read `skills/officecli-pptx/SKILL.md` first. This skill assumes you know how to: add slides + shapes + charts + connectors; address by `@name=` / `@id=`; quote paths; use `batch` heredocs; use `tailEnd=triangle` on flow connectors; run the Delivery Gate 1–5a; attribute `[AGENT-ERROR]` vs `[RENDERER-BUG]` vs `[SKILL gap]`. If any of those are unfamiliar, read pptx v2 first.\n\n**Inherited from pptx v2 (do NOT re-teach):**\n\n- Visual delivery floor — title ≥ 36pt / body ≥ 18pt / title ≥ 2× body, cover-richness, contrast floor, no `\\$\\t\\n` literals, ≤ 1 animation per slide / ≤ 600ms.\n- Grid math — 33.87 × 19.05cm, edge margin ≥ 1.27cm, inter-block gap ≥ 0.76cm, ≥ 20% negative space. For N-card grids: `col = (33.87 − 2·margin − (N−1)·gap) / N`.\n- Four canonical palettes (Executive navy / Forest & moss / Warm terracotta / Charcoal minimal) — morph decks may pick a different mood from `reference/styles/`, but contrast rules still apply.\n- Chart-choice table — column vs bar vs line vs pie vs scatter vs large-text KPI; `> 3 series + > 8 categories` = split.\n- Connector canon — `shape=straight|elbow|curve`, `@id=` for from/to (C-P-6), `tailEnd=triangle` on every flow.\n- Shell escape 3-layer — `$` single-quoted, heredocs for batch, `<a:br/>` for real newlines.\n- Resident mode + batch ≤ 12 ops, `<<'EOF'` single-quoted delimiter.\n- Delivery Gate 1-5a (schema, token grep, hyperlink rPr, slide-order, dark-on-dark) — every gate prints OK before declaring done.\n- Known Issues C-P-1..7 (hyperlink rPr, chart spPr warning, animation duration readback, animation remove, connector enum, connector `@name=`, chart color renderer normalization).\n- Attribution triage — `[AGENT-ERROR]` vs `[RENDERER-BUG]` vs `[SKILL gap]`.\n\n**Morph identity — what this skill owns (delta on top of pptx v2):**\n\n- **Cross-slide shape-name binding.** PowerPoint's Morph engine pairs shapes by **identical `name=`** across adjacent slides and interpolates their position / size / rotation / fill / opacity. No matching name ⇒ no animation, silent fade. This is a workflow discipline, not a CLI feature.\n- **Namespace prefixes:** `!!scene-*` (persistent decoration, never ghosted) / `!!actor-*` (content that evolves then exits) / `#sN-*` (per-slide content, ghosted on slide N+1). Plan the names BEFORE you `add`.\n- **Ghost position `x=36cm`** (off the right edge of the 33.87cm canvas). Never delete a `!!`-prefixed shape — move it off-canvas so the morph exit animation still plays.\n- **`transition=morph` auto-prefix quirk.** The CLI auto-prepends `!!` to every shape on a morph slide (`#s1-title` is stored as `!!#s1-title`). `@name=` path selectors **still resolve** — `get .../shape[@name=#s1-title]` returns the shape (matching is suffix/prefix-tolerant). The name you read back is the prefixed form. See §Known Issues.\n- **Adjacent-slide spatial variety.** Displacement ≥ 5cm or rotation ≥ 15° between pairs — otherwise morph interpolates nothing visible.\n- **Renderer reality.** Morph renders in PowerPoint 365 / Keynote / WPS. LibreOffice and many web viewers render as plain fade (runtime feature). Not a skill defect — `[RENDERER-BUG]`.\n\n### Reverse handoff — when to go BACK to pptx base (or sibling skills)\n\nStay in **pptx v2 base** for any deck without cross-slide motion (board reviews, sales decks, all-hands, training). Stay in **officecli-pitch-deck** for fundraising narrative arcs without morph. Use this skill only when the user explicitly asks for \"morph\" / \"smooth transitions\" / \"continuous animation\" AND ≥ 2 consecutive slides share a visual element that transforms. \"Animated deck\" meaning one-off entrance animations → pptx v2 §Animations, not morph.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see pptx v2 §Shell & Execution Discipline. Same rules verbatim.\n\n**Morph-specific additions:**\n\n- **`!!` in shell values — single-quote.** Bash / zsh history expansion eats unquoted `!!foo`. Always use `--prop 'name=!!scene-ring'` (single quotes). In Python `subprocess.run([...])` lists, no quoting needed — pass `\"name=!!scene-ring\"` as a plain string.\n- **`$` in prop text — single-quote (price tokens).** `--prop text='$9/mo'` and `--prop text='$199/yr'` — NEVER `--prop text=\"$9/mo\"` (zsh/bash eat `$9` as empty var → text rendered as `.` / stray period). Same for `${VAR}`, `$USER`, `\\n`, `\\r`, `\\t` inside a double-quoted prop. Gate 2 morph addendum below greps for the leak signature.\n- **`#` in shell values — safe, but quote anyway.** `#` is a comment leader only at the start of a shell word. `--prop name=#s1-title` works, but `--prop 'name=#s1-title'` is the habit that stops you guessing.\n- **Batch heredoc is the cleanest path for multi-shape slides.** `<<'EOF' | officecli batch $FILE` disables all shell expansion — safe for `$`, `!!`, `#`, `'` inside the JSON body.\n- **`--json` responses wrap the payload in `.data.results[]`.** Both `query` and `get` return a `.data.results[]` array. A single node's `format` sits at `.data.results[0].format.X`; that node's children sit at `.data.results[0].children[]` (each child's format at `.data.results[0].children[].format.X`). Always go through `.data.results[0]` — bare `.data.children[]` or `.data.format` returns null silently.\n- **Variable:** `FILE=\"deck.pptx\"` at the top of every build script; every example below uses `$FILE`.\n- **Gate shell pattern — COUNT, then if/else.** Never write `grep … && echo LEAK || echo OK` — when grep exits 1 (0 matches), the `||` branch fires with empty stdout and prints \"OK\" confusingly (or prints \"LEAK\" from prior pipes). Canonical form: `COUNT=$(cmd | wc -l); if [ \"$COUNT\" -gt 0 ]; then echo \"LEAK: …\"; else echo \"OK\"; fi`.\n\n## Two primitives this skill owns\n\n- **Scene Actors** = persistent `!!`-named shapes (decoration or content) **paired by identical name** across adjacent slides so Morph can interpolate them. Every `!!scene-*` / `!!actor-*` shape is a scene actor.\n- **Choreography** = the plan for how actors evolve — who moves where, who enters, who exits, on which slide pair. Written BEFORE code in the §Morph Pair Planning table.\n\nUse this skill when the user asks for morph motion AND ≥ 2 consecutive slides share a visual element that transforms. Target-viewer caveat: morph needs PowerPoint 365 / Keynote / WPS — if the user is LibreOffice-only, warn first (see §Renderer honesty).\n\n**Speaker notes rule.** Every content slide (non-cover, non-closing) MUST carry speaker notes via `officecli add \"$FILE\" /slide[N] --type notes --prop text='…'`. Missing notes = not shippable — inherits pptx v2 §Hard rules (H7). Morph decks tend to be visually minimal, so notes carry the narration.\n\n## What is Morph? (core mechanics)\n\nPowerPoint's Morph transition creates smooth motion by interpolating shape properties between adjacent slides, matched by **identical shape names**.\n\n```\nSlide 1: shape name=\"!!scene-ring\" x=5cm  width=8cm   fill=E94560 opacity=0.3\nSlide 2: shape name=\"!!scene-ring\" x=20cm width=12cm fill=E94560 opacity=0.6\n         ↓  transition=morph on slide 2\nResult:  Ring smoothly moves, grows, and fades darker over ~1 second\n```\n\nMorph only runs if slide N+1 carries `transition=morph`. Apply it via `officecli add / --type slide --prop transition=morph` on creation, or `officecli set \"/slide[N]\" --prop transition=morph` after the fact. Slides 2+ that omit this prop fall back to whatever the master defines (usually no transition) — motion dies silently.\n\n**Three-prefix naming system (non-negotiable):**\n\n| Prefix | Role | Lifecycle | Example |\n|---|---|---|---|\n| `!!scene-*` | Background / decoration — persists across the entire deck | Set once, adjust position/size to create motion; **rarely ghosted** | `!!scene-ring`, `!!scene-bg-band`, `!!scene-grid` |\n| `!!actor-*` | Content / foreground — evolves across a section | Introduced on slide N, modified on slide N+1, N+2…, **ghosted to `x=36cm`** on its exit slide | `!!actor-feature-box`, `!!actor-metric`, `!!actor-headline` |\n| `#sN-*` | Per-slide content (titles, bullets, captions) | Added fresh on slide N, **ghosted to `x=36cm`** on slide N+1 | `#s1-title`, `#s2-kpi`, `#s3-caption` |\n\n**Hard rule:** `!!scene-*` and `!!actor-*` names must NEVER collide (e.g., `!!scene-card` + `!!actor-card` in the same deck — morph engine confuses them). Disambiguate: `!!scene-card-bg` vs `!!actor-card-content`.\n\n**Charts can be morph-paired.** `officecli add … --type chart` accepts `--prop name=!!…` (the name reads back), so a chart with an identical `!!`-name on adjacent slides participates in shape-name morph pairing — the chart frame interpolates position / size. Note morph cannot interpolate the *plotted data* inside the chart frame. For bar-grow / line-grow narratives where the bars themselves must animate: (a) accept plain fade-in of the chart as-is, OR (b) build N `!!actor-bar-K` rectangles manually sized to the values and morph those — each rect carries the same `!!actor-bar-K` name across adjacent slides while width / height / fill evolves.\n\n**Ghost accumulation is silent.** Once a `!!`-prefixed shape appears on any slide, it stays visible on every subsequent morph slide unless explicitly moved to `x=36cm`. `final-check` helper does NOT detect `!!` shapes lingering in the visible area — **only Gate 5b screenshot audit does.** Plan every actor's exit slide in the pair table BEFORE coding.\n\n**Spatial variety rule.** Adjacent slides must have **noticeably different** compositions — displacement ≥ 5cm OR rotation ≥ 15° OR size delta ≥ 30% on at least 3 morph-paired shapes. Without this, morph interpolates nothing visible and the transition collapses to a fade (silent-fail).\n\n**Simultaneous-timing constraint.** All `!!` shapes in one morph pair animate simultaneously. To stagger shape A before shape B, insert an intermediate keyframe slide — there is no per-shape delay knob.\n\n**Paired vs enter vs exit — three behaviors, one rule.** Same mechanism (shape-name match) produces three outcomes:\n\n| Behavior | Source slide A | Target slide B | Who carries `!!`? |\n|---|---|---|---|\n| **Paired morph** (interpolate) | has `!!foo` | has `!!foo` | both slides, identical name |\n| **Enter** (fade / morph-in) | — (no counterpart) | has `!!foo` | target only — new shape |\n| **Exit via ghost** (slide off) | has `!!foo` at visible `x` | has `!!foo` at `x=36cm` | both — same name, B is off-canvas |\n\n**Outgoing content (not incoming) is what gets `!!`-prefixed + ghosted.** `!!actor-*` shapes silently \"disappear\" when you forget them — their name going missing on slide B reads as an unpaired exit (plain fade). Always explicit-ghost to `x=36cm` so the exit animation slides off the right edge visibly. One runnable example:\n\n```bash\n# Slide 2: actor is visible at x=5cm — Slide 3: same name, ghosted off-canvas → visible slide-off motion\nofficecli add \"$FILE\" \"/slide[3]\" --type shape --prop 'name=!!actor-metric' \\\n  --prop text=\"42%\" --prop x=36cm --prop y=8cm --prop width=6cm --prop height=3cm\n```\n\n**Content (`#sN-*`) is added fresh per slide.** Because text changes every slide, Morph has no meaningful pairing to do on titles / body — it cross-fades them. This is why `#sN-*` get different names per slide (they are intentionally unpaired) and must be ghosted on slide N+1. Scene actors (`!!`) carry the continuity; content (`#`) carries the message.\n\n## Morph Pair Planning (pre-code, REQUIRED)\n\nBefore planning morph pairs, if the deck's audience / purpose / narrative is underspecified, run the planning prompt in `reference/decision-rules.md` to emit a `brief.md` first — a morph arc without a narrative spine collapses into \"slide with motion\", not \"story with motion\".\n\nPlan every transition in a table inside `brief.md` **before** writing any `officecli add`. Renaming shapes mid-build is the #1 cause of ghost accumulation bugs.\n\n| Pair | Slide A (start) | Slide B (end) | Actors in play | Ghost on Slide B |\n|---|---|---|---|---|\n| 1→2 | `!!scene-ring` centered 5cm, `#s1-title` visible | Ring shifts to x=20cm, grows 8→12cm; `#s2-subtitle` revealed | `!!scene-ring` evolves | `#s1-title` → x=36cm |\n| 2→3 | `!!actor-feature-box` large (14cm wide) | Feature box small (6cm), `!!actor-metric` enters | `!!scene-ring`, `!!actor-feature-box`, `!!actor-metric` | `#s2-subtitle` → x=36cm |\n| 3→4 | Content section A | Section B divider | — | `!!actor-feature-box` + `!!actor-metric` → x=36cm (section-exit); `#s3-*` → x=36cm |\n\n**Planning rules:**\n\n1. Decide ALL `!!` names up front — each morph-paired shape must use the **exact same name** on both slides.\n2. Classify every `!!` shape as `!!scene-*` or `!!actor-*`. Scene shapes persist; actors must have a planned exit slide.\n3. **Section-transition boundary:** when moving into a new topic section, ghost ALL previous-section `!!actor-*` on the first slide of the new section. Only `!!scene-*` (whole-deck decoration) remains.\n4. Do NOT start building until the table is complete. If the plan changes mid-build, redraw the table and re-verify affected slides.\n\n## Morph Recipes (4 patterns)\n\nFour patterns cover ~95% of morph decks. `$FILE=\"deck.pptx\"` throughout. Each block is self-contained and ≤ 20 lines.\n\n### (a) Single-element morph — size / position\n\n**Visual outcome.** A hero title centered on slide 1 (size 48pt at y=8cm), then slide 2 shrinks it to 32pt and shifts it to the top-left corner (x=1.5cm, y=1cm) — letting fresh slide-2 content take center stage. One shape, clean motion, no actors.\n\n```bash\nFILE=\"deck.pptx\"\nofficecli create \"$FILE\"; officecli open \"$FILE\"\n\n# Slide 1 — hero\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!actor-headline' \\\n  --prop text=\"The one idea\" --prop x=4cm --prop y=8cm --prop width=26cm --prop height=3cm \\\n  --prop font=Georgia --prop size=48 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\n\n# Slide 2 — headline shrinks + moves; new body takes stage\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761 --prop transition=morph\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!actor-headline' \\\n  --prop text=\"The one idea\" --prop x=1.5cm --prop y=1cm --prop width=12cm --prop height=1.5cm \\\n  --prop font=Georgia --prop size=24 --prop bold=true --prop color=FFFFFF --prop align=left --prop fill=none\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=#s2-body' \\\n  --prop text=\"Here is the supporting evidence.\" --prop x=1.5cm --prop y=5cm --prop width=30cm --prop height=2cm \\\n  --prop font=Calibri --prop size=20 --prop color=CADCFC --prop fill=none\n\nofficecli close \"$FILE\"; officecli validate \"$FILE\"\n```\n\n### (b) Multi-element coordinated morph — Actors / Choreography\n\n**Visual outcome.** Three scene actors (`!!scene-ring`, `!!scene-dot`, `!!scene-band`) repositioned across 3 slides to feel like a camera pan. Fresh per-slide titles fade in / out via the `#sN-*` ghost pattern. Use this when the narrative has a continuous visual backdrop.\n\n```bash\n# Slide 1 — anchor composition (already built via recipe a; here we add actors)\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!scene-ring' --prop preset=ellipse \\\n  --prop fill=E94560 --prop opacity=0.3 --prop x=5cm --prop y=3cm --prop width=8cm --prop height=8cm\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!scene-dot' --prop preset=ellipse \\\n  --prop fill=0F3460 --prop x=28cm --prop y=15cm --prop width=1cm --prop height=1cm\n\n# Slide 2 — morph: ring moves + grows, dot slides left (spatial variety ≥ 5cm on both)\nofficecli set \"$FILE\" \"/slide[2]\" --prop transition=morph\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!scene-ring' --prop preset=ellipse \\\n  --prop fill=E94560 --prop opacity=0.6 --prop x=20cm --prop y=2cm --prop width=12cm --prop height=12cm\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!scene-dot' --prop preset=ellipse \\\n  --prop fill=0F3460 --prop x=3cm --prop y=16cm --prop width=1.5cm --prop height=1.5cm\n# Ghost slide-1 content (name path still resolves after morph — see Known Issues)\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=#s1-title]\" --prop x=36cm 2>/dev/null || true\n\n# Verify morph pair: identical names on slides 1 & 2\nofficecli get \"$FILE\" /slide[1] --depth 1 --json | jq -r '.data.results[0].children[]?.format.name // empty'\nofficecli get \"$FILE\" /slide[2] --depth 1 --json | jq -r '.data.results[0].children[]?.format.name // empty'\n# Compare — `!!scene-ring` and `!!scene-dot` MUST appear on both, byte-identical.\n# Note: morph stores names with a `!!` prefix; compare the prefixed forms.\n```\n\n### (c) Continuous multi-slide morph (story arc) — use helpers\n\n**Visual outcome.** A 5-slide arc telling one continuous story: same 2 scene actors drift across the canvas as the narrative progresses; content (`#sN-*`) refreshes per slide and is ghosted on the next. Building this by hand is ~60 commands — use `reference/morph-helpers.py` to keep the build script short and auto-verified.\n\n```python\n#!/usr/bin/env python3\n# Invoke the provided helper library for clone + ghost + verify\nimport subprocess, sys, os\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\nHELPERS = os.path.join(SCRIPT_DIR, \"reference\", \"morph-helpers.py\")\nFILE = \"deck.pptx\"\n\ndef helper(*args):\n    subprocess.run([sys.executable, HELPERS, *[str(a) for a in args]], check=True)\n\n# ... assume slide 1 is built with 2 scene actors (!!scene-ring, !!scene-dot) + #s1-title\n# Helper builds slide 2–5 with: clone from previous + apply transition=morph + ghost previous #sN- content\n# `clone` prints the cloned slide's shape list — read it to pick which shape indices carry the\n# previous slide's #s(n-1)- content, then pass those explicit indices to `ghost`.\nfor n in range(2, 6):\n    helper(\"clone\", FILE, n - 1, n)          # clone + set transition=morph + list shapes (note the #s(n-1)- indices)\n    helper(\"ghost\", FILE, n, 1, 2)           # ghost the #s(n-1)- content shapes by index (here shapes 1 & 2)\n    # …then add THIS slide's #sN- content via officecli add as normal…\nhelper(\"final-check\", FILE)                   # structural pass; DOES NOT catch !! lingering in visible area\n```\n\nHelper signatures and source: `reference/morph-helpers.py` (`clone`, `ghost`, `verify`, `final-check`). The shell equivalent is `reference/morph-helpers.sh` — pick one per platform; do not mix.\n\n**When to use helpers vs raw `officecli`.** For 2-3 slide decks, raw commands (recipes a, b) are clearer. For 5+ slides with repeating clone/ghost/verify cadence, helpers save ~40% of commands and provide built-in verification. Every slide is still closed by `officecli validate` before delivery.\n\n### (d) Morph + fade hybrid — entrance on morph slide\n\n**Visual outcome.** A morph pair where `!!scene-ring` moves continuously while a NEW per-slide card fades in simultaneously. Used when a morph-paired backdrop carries the eye and fresh foreground content needs a softer entrance than a raw appearance.\n\n```bash\n# Slide 2 already has transition=morph and !!scene-ring. Add a new card with fade-entrance.\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=#s2-card' --prop preset=roundRect \\\n  --prop fill=F5F7FA --prop line=none --prop x=2cm --prop y=12cm --prop width=10cm --prop height=5cm\n\n# Apply simultaneous-with-morph fade entrance to the new card.\n# 'fade-entrance-300-with' = fade in, 300ms, trigger=withPrevious (plays with the morph transition).\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=#s2-card]\" --prop animation=fade-entrance-300-with\nofficecli get \"$FILE\" \"/slide[2]/shape[@name=#s2-card]\" --json | jq '.data.results[0].format.animation'  # readback sanity — drops the trigger suffix, reads back as \"fade-entrance-300\"\n```\n\n**Why this works.** Morph animates the `!!scene-*` shapes only (they have a pair on slide 1); the new `#s2-card` has no slide-1 counterpart, so morph would default-fade it — `fade-entrance-300-with` makes that fade explicit and timed. Keep the animation per pptx v2 floor: ≤ 600ms, no bounce / swivel / fly-from-edge (`officecli help pptx animation` for the canonical preset list).\n\n## Choreography — animation types + staggered timing\n\nHow morph animates multiple shapes determines what the audience sees. Pick the right mechanism for each pair:\n\n| Animation type | How to achieve it (between Slide A and Slide B) |\n|---|---|\n| Simple move | Same `!!` name on both slides, same size, different `x`/`y` — morph interpolates position |\n| Scale transform | Same name, different `width`/`height` — morph interpolates size (and re-positions the center) |\n| Move + scale | Different `x`, `y`, `width`, `height` simultaneously — morph handles all dimensions at once |\n| Color / opacity shift | Same name, different `fill` or `opacity` — morph cross-fades the fill |\n| Rotation | Same name, different `rotation` (degrees) — morph rotates along the shortest arc |\n| Font size change | Same name, different `size` (pt) on text shape — interpolates in PowerPoint 365; less reliable on Keynote / WPS / LibreOffice (may degrade to crossfade). For portable motion, pair `size` change with a matching `width`/`height` delta or an `x`/`y` displacement — the spatial change keeps motion visible when size interpolation drops out |\n| Enter (fade in) | Shape exists only on Slide B (no counterpart on A) — morph fades it in |\n| Exit (fade out) | Shape exists only on Slide A (no counterpart on B) — morph fades it out |\n\n**Multi-shape timing constraint.** All `!!` shapes in one morph pair animate **simultaneously** — there is no per-shape delay / duration knob in the CLI (help confirms: no `morph.duration` / `morph.delay` on slide). To stagger shape A before shape B, **split the transition into two pairs** with an intermediate slide:\n\n```\nSlide 2 → Slide 3:  !!actor-A moves (!!actor-B stays put)\nSlide 3 → Slide 4:  !!actor-B moves (!!actor-A stays put or ghosts)\n```\n\nSlide 3 is an explicit intermediate keyframe. Do NOT attempt to fake staggering via timing props on the shape's `animation=` prop — Morph runs before per-shape animations.\n\n**Good-enough variety heuristic (Best Practice — creative flexibility).** For a morph to read as \"motion\", change at least 3 of {x, y, width, height, rotation, fill, opacity} on the dominant paired shape, with displacement ≥ 5cm OR rotation ≥ 15° OR size delta ≥ 30%. One shape × 3 props is a valid creative pattern (focus on one hero element).\n\n**Delivery Gate 5b-morph-2 is stricter.** The gate hard-asserts ≥ 3 DIFFERENT `!!`-prefixed shapes each vary by ≥ 1 of {x, y, width, height, rotation, font-size} across the pair — integrity check for \"is this really a morph or a pretend-morph\". Heuristic informs creative intent; Gate decides delivery. **Brand-constant scenery (pinned header strip, footer bar, logo badge) does NOT count toward the 3-shape quota** — these are supposed to stay put; motion must come from 3 other named shapes. When in doubt, satisfy the stricter Gate.\n\n**Deck-length rhythm.** Filling every transition with morph reads as anxious, not cinematic. Pace morph moments to deck length:\n- **8-10 slides (dense):** 3-5 morph moments; motion can cluster.\n- **12-18 slides (ceremonial):** 3-5 TOTAL morphs, spaced every 4-6 slides; use `transition=morph` at section dividers so the animation reads as chapter punctuation, not continuous agitation.\n- **18+ slides (Act-based):** structure into 3 acts with 1 long section-divider morph between acts (5-10s of deliberate motion with a brief hold), plus 2-3 quieter morphs inside each act. Lean heavier on `!!scene-*` continuity than per-slide `!!actor-*` churn.\n\n## Scene-actor spatial rule\n\nScene actors and actors moving across the canvas MUST stay in predictable zones during morph — otherwise they cross over content and read as clutter.\n\n**Safe zones (prefer for scene actor rest positions and morph paths):**\n\n```\nTop-right corner:   x ≥ 24cm, y ≤ 6cm\nBottom-right:       x ≥ 24cm, y ≥ 12cm\nBottom-left:        x ≤ 2cm,  y ≥ 12cm\nOff-canvas (ghost): x ≥ 33.87cm  (canvas right edge; use x=36cm for explicit ghost)\n```\n\n**Avoid resting actors in the content core:** `x = 2~28cm, y = 3~16cm`. Actors may **pass through** the core during morph (that's the motion), but they should not end a slide parked there with high opacity unless they are content themselves (`!!actor-*` carrying the slide's message).\n\n**Before placing any scene actor, inspect existing shape bounds:**\n\n```bash\nofficecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n  jq -r '.data.results[0].children[]? | \"\\(.format.name // .path)  x=\\(.format.x) y=\\(.format.y) w=\\(.format.width) h=\\(.format.height)\"'\n```\n\nConfirm the actor's target position does not overlap any `#sN-*` content shape's bounding box (`x` to `x + width`, `y` to `y + height`). If it would overlap, lower actor `opacity` ≤ 0.15 OR move it to a safe zone.\n\n## Style library lookup workflow\n\n`reference/styles/` holds 52 visual style directories (dark / light / warm / vivid / bw / mixed moods) — design inspiration, not templates. Use the library as **on-demand reference**, not as a content dump.\n\n**Why lookup, not copy.** Each of the 52 `build.sh` files is a complete style demo — but the coordinates were hand-tuned for that specific demo's content length. Copying them verbatim into a deck with different content produces overlaps and misalignment (flagged in `INDEX.md` L5-11). The library's value is the **design logic**: palette choice for a mood, signature shape, choreography pattern. Apply that logic to your own grid math.\n\n**Four-step lookup:**\n\n1. **Browse INDEX.** `reference/styles/INDEX.md` groups all 52 styles by palette category and mood (e.g. `dark--premium-navy` = authoritative / refined; `warm--earth-organic` = organic / grounded). The Quick Lookup table also shows each style's **primary hex trio** (bg / fg / accent) — if the user specified a brand color, scan the hex column to find the nearest match without opening every `style.md`. Pick 1 style that matches the topic mood OR aligns with the user-specified hex.\n2. **Read philosophy.** Open `reference/styles/<style-id>/style.md` for design intent — type pairing, color logic, signature elements.\n3. **Glance technique.** Open `reference/styles/<style-id>/build.sh` ONLY for technique reference (signature shapes, palette hex codes, choreography ideas) — **coordinates are known-buggy per `INDEX.md` L5-11**; do not copy them.\n4. **Apply on your own canvas.** Build your deck using pptx v2 grid math + visual floor; borrow only the palette and the signature gesture.\n\n**Pointer:** `→ see reference/styles/<style-id>/` — never inline-copy coordinates from a style build.sh.\n\n## Delivery Gate (inherits pptx v2 + morph additions)\n\n**Gate 1–5a: full port from pptx v2.** → see pptx v2 §Delivery Gate. Schema (whitelisting C-P-2 chart spPr), token grep (`$…$` / `{{…}}` / `\\$\\t\\n` / `()` / `[]`), hyperlink rPr (C-P-1), slide-order sanity, dark-on-dark contrast (Gate 5a). **Refuse to declare done until every pptx Gate 1–5a prints its OK message.** Morph decks have the same token / schema / order risks as any pptx.\n\n### Gate 2 morph addendum — price / metric tokens eaten by zsh\n\nPptx v2 Gate 2 covers `$…$`, `{{…}}`, `\\$\\t\\n` literals, empty `()` / `[]`. Morph decks add a class of leaks: price / metric tokens (`$9/mo`, `$29/month`, `$199/yr`) written in double-quoted `--prop text=\"…\"` — the shell eats `$9` as an empty variable and the CLI stores `/mo` or a stray period. Run this in addition to pptx Gate 2:\n\n```bash\n# Gate 2 morph — price / metric token leaks + stray-period placeholders\n# Pattern hits: bare prices ($9, $29, $9.99), /unit suffix ($9/mo, $199/yr), ${VAR}, \\n/\\r/\\t, lone period\nLEAKS=$(officecli view \"$FILE\" text | grep -nE '\\$[0-9]+(\\.[0-9]+)?(/(mo|month|yr|year|day|wk|week|hr|hour))?|\\$\\{[A-Z_]+\\}|\\\\[nrt]|^\\.$' || true)\nif [ -z \"$LEAKS\" ]; then echo \"Gate 2 morph OK\"; else echo \"LEAK: $LEAKS\"; fi\n```\n\nCovers: `$9` `$9.99` `$29/month` `$199/yr` `$1/day` `${VAR}` `\\n`/`\\r`/`\\t` literals + stray `.` placeholders. Fix: single-quote the prop (`--prop text='$9/mo'`).\n\n### Gate 5b — Visual audit via HTML preview (MANDATORY) — extended for morph\n\nRun `officecli view \"$FILE\" html` and Read the returned HTML path. For every slide, answer the pptx v2 Gate 5b questions (overlap / dark-on-dark / divider overlap / order sanity / missing arrowheads) PLUS these four morph-specific checks:\n\n**Important: selectors with prefix match.** `officecli query` only supports operators `=`, `!=`, `~=`, `>=`, `<=`, `>`, `<` — there is NO `^=` prefix operator. A selector like `shape[name^=!!actor-]` returns an `invalid_selector` error. For \"starts-with\" filtering, use a `get --depth 1` loop + `jq startswith()` as shown below.\n\n- **5b-morph-1 — `!!actor-*` leak into visible area after its section ends.** For every `!!actor-*` that should have exited, confirm `x ≥ 33.87cm` (canvas right edge). Loop + filter (selector-safe):\n  ```bash\n  NSLIDES=$(officecli query \"$FILE\" slide --json | jq '.data.results | length')\n  for N in $(seq 1 $NSLIDES); do\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r --arg n \"$N\" '.data.results[0].children[]? |\n        select(.format.name? // \"\" | startswith(\"!!actor-\")) |\n        select((.format.x // \"0cm\" | rtrimstr(\"cm\") | tonumber) < 33.87) |\n        \"slide \\($n) leak: \\(.format.name) stuck at x=\\(.format.x)\"'\n  done\n  ```\n  Any line printed = actor stuck visible. `final-check` misses this — only the loop + Read HTML do.\n\n- **5b-morph-2 — Adjacent slides have identical spatial composition (no motion).** Hard rule: between every morph pair, ≥ 3 DIFFERENT `!!`-prefixed shapes must each differ by ≥ 1 of {x, y, width, height, rotation, font-size}. Proof loop (dump both slides, diff same-name shapes, count differing shapes):\n  ```bash\n  for K in 1 2 3 4; do\n    A=$(officecli get \"$FILE\" \"/slide[$K]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) |\n        \"\\(.format.name)|\\(.format.x)|\\(.format.y)|\\(.format.width)|\\(.format.height)|\\(.format.rotation // 0)\"')\n    B=$(officecli get \"$FILE\" \"/slide[$((K+1))]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) |\n        \"\\(.format.name)|\\(.format.x)|\\(.format.y)|\\(.format.width)|\\(.format.height)|\\(.format.rotation // 0)\"')\n    VARIES=$(diff <(echo \"$A\") <(echo \"$B\") | grep -c '^[<>]')\n    if [ \"$VARIES\" -lt 6 ]; then echo \"pair $K→$((K+1)) FLAT: only $VARIES diff-lines (need ≥ 6 = 3 shapes × 2 sides)\"; fi\n  done\n  ```\n\n- **5b-morph-3 — Morph-pair name mismatches.** Adjacent slides must share at least 2 `!!`-prefixed names exactly. Proof (note: children live at `.data.results[0].children[]` — bare `.data.children[]` returns null):\n  ```bash\n  for N in 1 2 3 4 5; do\n    echo \"--- slide $N ---\"\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) | .format.name'\n  done\n  ```\n  Visually compare sequential blocks — shared `!!` names between N and N+1 are the morph pairs. Zero overlap = the pair is a plain fade.\n\n- **5b-morph-4 — `#sN-*` lingering on slide N+1 (ghost leak).** Per-slide content MUST be ghosted (`x=36cm`) on the NEXT slide. Loop + filter per N≥2:\n  ```bash\n  NSLIDES=$(officecli query \"$FILE\" slide --json | jq '.data.results | length')\n  for N in $(seq 2 $NSLIDES); do\n    PREV=$((N-1))\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r --arg n \"$N\" --arg p \"$PREV\" '.data.results[0].children[]? |\n        select(.format.name? // \"\" | startswith(\"#s\\($p)-\")) |\n        select((.format.x // \"0cm\" | rtrimstr(\"cm\") | tonumber) < 33.87) |\n        \"slide \\($n) leak: \\(.format.name) stuck at x=\\(.format.x)\"'\n  done\n  ```\n  Any line printed = a `#s(N-1)-*` shape stayed visible on slide N. Ghost it.\n\n**REJECT the delivery** if any 5b-morph-1..4 loop prints a line. Collect stdout from all four loops into one stream and enforce with the COUNT pattern: `LEAK_COUNT=$(...all four loops... | wc -l); if [ \"$LEAK_COUNT\" -gt 0 ]; then echo \"REJECT: $LEAK_COUNT morph leaks\"; else echo \"Gate 5b-morph OK\"; fi`.\n\n## Renderer honesty\n\n**Morph renders in:** PowerPoint 365 (Windows/Mac), Keynote, WPS, PowerPoint Online.\n\n**Morph does NOT render in:** LibreOffice Impress (renders static, sometimes as fade), Google Slides web viewer (loses interpolation), most HTML / SVG viewers, `officecli view html` (structural only — morph is runtime). This is `[RENDERER-BUG]`, not a skill defect. Tell the user explicitly: \"Open in PowerPoint 365 / Keynote / WPS to see the morph motion; other viewers will show static or plain fade.\"\n\nStatic screenshots from any renderer **cannot verify morph motion** (the motion only exists at runtime). Use Gate 5b queries above to prove pair correctness; use a live viewer to prove motion quality.\n\n## Ghost Discipline & Actor Lifecycle\n\n**Every `!!actor-*` and `#sN-*` shape must be managed across EVERY slide, not just its \"exit\" slide.**\n\n### The Per-Slide Ghosting Rule\n\nWhen building a multi-slide morph deck:\n1. **Slide N: Introduce `!!actor-ring` (visible at x=0cm)**\n2. **Slide N+1: Add new content. Before finishing, ghost `!!actor-ring` to `x=36cm`.**\n3. **Slide N+2: Add more content. Re-ghost `!!actor-ring` to `x=36cm` again.** (Not optional — even though it was already off-screen, each slide is a fresh canvas.)\n4. **Slide N+3: If `!!actor-ring` should be visible again, move it back to x=0cm or its new position.**\n\n**Why:** Each slide's shape list is independent. Moving a shape off-canvas on slide N does NOT carry over to slide N+1 — if you forget to re-ghost it, it will re-appear at its original position on N+1.\n\n### Workflow Pattern (Bash)\n\n```bash\n# After adding new content shapes to slide $SLIDE:\nfor ACTOR in \"!!actor-ring\" \"!!actor-dot\" \"!!actor-accent-bar\"; do\n  officecli set \"$FILE\" \"/slide[$SLIDE]/shape[@name=$ACTOR]\" --prop x=36cm || true\ndone\n```\n\nOr in a build loop:\n\n```bash\nfor SLIDE_NUM in 3 4 5 6 7 8 9 10 11; do\n  # Add content specific to this slide\n  officecli add \"$FILE\" \"/slide[$SLIDE_NUM]\" --type shape ...\n  \n  # IMMEDIATELY ghost all old actors (M-2 prevention)\n  officecli set \"$FILE\" \"/slide[$SLIDE_NUM]/shape[@name=!!actor-ring]\" --prop x=36cm || true\n  officecli set \"$FILE\" \"/slide[$SLIDE_NUM]/shape[@name=!!actor-dot]\" --prop x=36cm || true\ndone\n```\n\n### Detection: Ghost Count Gate\n\n`morph-helpers.py final-check` counts all shapes at `x ≥ 34cm`. If count > 50, it prints:\n```\nREJECT: Found 135 accumulated ghosts — likely M-2 ghost accumulation.\nRun: officecli query deck.pptx 'shape[x>=34cm]' --json | jq '.data.results | length'\nExpected ≤ 50 (roughly 4–5 active actors × 10–12 slides).\n```\n\n**Fix:** Review the build log, ensure every slide re-ghosts all actors that should not appear in it. Re-run final-check. If still > 50, use `morph-helpers.py clean-accumulation deck.pptx` (see reference section).\n\n## Common Morph Pitfalls (design + workflow traps)\n\nBase pptx pitfalls (shell quoting, zsh `[N]` globbing, hex `#` prefix, `\\n` in prop text) → see pptx v2 §Common Pitfalls. These are the morph-specific traps:\n\n| Pitfall | Correct approach |\n|---|---|\n| `!!scene-card` and `!!actor-card` in the same deck | Names must be unique across prefixes. Rename: `!!scene-card-bg` vs `!!actor-card-content` |\n| Renaming shapes mid-build after some slides are already done | Ghost accumulation bug waiting to happen. Stop, redraw the §Morph Pair Planning table, rerun affected slides |\n| Placing `!!actor-*` into the content core without planning an exit | Every `!!actor-*` needs a ghost slide. Plan it in the pair table BEFORE coding |\n| **Ghost accumulation (M-2): forgetting to re-ghost `!!actor-*` on later slides** | **CRITICAL:** When you add new content to slide N+1, ALL `!!actor-*` from slide N that should not be visible must be moved to `x=36cm` again. Do NOT assume they stay off-screen once ghosted — each slide is independent. Build pattern: `for each new slide: add content shapes → then loop: set each active !!actor-* to x=36cm`. `morph-helpers.py final-check` will REJECT if ghost count exceeds 50. |\n| Forgetting `transition=morph` on a slide | Silent fade. Gate 5b-morph-2 (no motion) catches it; fix via `set /slide[N] --prop transition=morph` |\n| Assuming `@name=` paths break on a morph slide | They do not — `@name=` still resolves after `transition=morph` (M-1); only the *readback* name gains a `!!` prefix |\n| Adjacent slides visually identical | Morph has nothing to interpolate — collapses to plain fade. Apply §Scene-actor spatial rule and move ≥ 3 shapes by ≥ 5cm / ≥ 15° |\n| Trying to stagger 2 shapes via per-shape timing | Not supported — split the pair into two transitions with an intermediate keyframe slide |\n| Testing morph motion in LibreOffice or a browser | `[RENDERER-BUG]`, not skill defect. Test in PowerPoint 365 / Keynote / WPS |\n| Deleting a `!!` shape on exit instead of ghosting it | Deletion breaks morph pairing — the shape vanishes without animation. Always ghost to `x=36cm` |\n| Writing `--prop text=\"$9/mo\"` with double quotes | Shell eats `$9` as empty variable → text stored as `/mo` or stray `.`. Use single quotes: `--prop text='$9/mo'`. Gate 2 morph addendum greps this leak. |\n| Using `<a:br/>` literal inside `--prop text='line1<a:br/>line2'` | Stored as 7 literal characters, not a line break. Use `officecli add \"/slide[N]/shape[@id=K]\" --type paragraph` once per line (M-6). |\n| Using `shape[name^=!!actor-]` selector | `officecli query` has no `^=` operator — returns `invalid_selector`. Use `get /slide[N] --depth 1 --json \\| jq '.data.results[0].children[]? \\| select(.format.name \\| startswith(\"!!actor-\"))'`. |\n\n## Known Issues & Pitfalls\n\nBase pptx bugs C-P-1..7 (hyperlink rPr, chart ChartShapeProperties warning, animation duration readback, animation remove, connector enum, connector `@name=`, chart-color renderer normalization) all apply. **→ see pptx v2 §Known Issues C-P-1..7 for workarounds.**\n\n**Morph-specific (M-1..5):**\n\n| # | Symptom | Workaround |\n|---|---|---|\n| **M-1** | After `officecli set '/slide[N]' --prop transition=morph`, every shape on that slide has `!!` auto-prepended to its name (`#s1-title` → `!!#s1-title`). The readback name is the prefixed form. **Selector filter caveat:** after auto-prefix, `!!#sN-caption` coexists alongside `!!actor-*` — filtering \"scene actors\" in `jq` with `startswith(\"!!\")` produces false matches on auto-prefixed content. Always filter with `startswith(\"!!actor-\")` or `startswith(\"!!scene-\")`, never bare `startswith(\"!!\")`. | `@name=` path selectors **still resolve** — `/slide[N]/shape[@name=#s1-title]` returns the shape (matching is suffix/prefix-tolerant), so no path rewrite is needed. When you need the prefixed name in a jq filter, account for the leading `!!`. |\n| **M-2 🚨** | **Ghost accumulation — `!!actor-*` introduced on slide 3 stays visible on slides 4, 5, 6 unless EXPLICITLY ghosted every page.** `final-check` helper detects this and rejects if ghost count > 50. | **MANDATORY per-slide rule:** After you add new content to a slide, immediately set ALL active `!!actor-*` from previous slides to `x=36cm` (or explicitly position them visible if they belong in the current context). Example: `officecli set /slide[4]/shape[@name=!!actor-ring] --prop x=36cm`. Run after EVERY slide addition, not just at the end. See §Ghost Discipline & Actor Lifecycle below. |\n| **M-3** | Section-transition boundary — on the first slide of a new topic section, previous-section `!!actor-*` shapes visibly linger. No command errors; only visual clutter. | On every section-start slide, explicitly ghost ALL `!!actor-*` from the previous section to `x=36cm`. Scene shapes (`!!scene-*`) stay. |\n| **M-4** | Agents sometimes invent `morph.duration=` / `transition.delay=` as independent props — they are rejected as UNSUPPORTED. | Use the combined shorthand on the `transition` prop: `transition=morph-slow` / `-fast`, or `transition=morph-<DUR_MS>` (e.g. `transition=morph-1500`). Both round-trip on readback (`transitionSpeed=slow`, `transitionDuration=1500`). Only beyond what the shorthand exposes (fine control of the raw XML), fall back to `raw-set` on `<p:transition>` — see M-4 example block below. |\n| **M-5** | `[RENDERER-BUG]` LibreOffice / Google Slides web viewer render morph slides as plain fade (no interpolation). | Test in PowerPoint 365 / Keynote / WPS. Not a skill defect — do not chase. |\n| **M-6** | `<a:br/>` written inside `--prop text='line1<a:br/>line2'` is stored as the literal 7-character string, NOT interpreted as a line break. Audience sees `line1<a:br/>line2` rendered verbatim. | For multi-line bullets / captions, add one paragraph per line: `officecli add \"/slide[N]/shape[@id=K]\" --type paragraph --prop text='line1'` then repeat with `text='line2'`. See pptx v2 §Shell escape for the real-newline workflow. |\n\n**M-4 example — slow down all morph transitions, raw-set fallback** (prefer `transition=morph-slow`; use this only for control beyond the shorthand). Note `//p:transition` matches both `mc:Choice` and `mc:Fallback` on a morph slide, yielding `2 element(s) affected`:\n\n```bash\n# Per-slide: add spd=\"slow\" to every transition element on slide N (2 XML hits per morph slide)\nfor N in 2 3 4; do\n  officecli raw-set \"$FILE\" \"/slide[$N]\" --xpath \"//p:transition\" --action setattr --xml 'spd=slow'\ndone\nofficecli validate \"$FILE\"\n```\n\nReadback: `officecli query \"$FILE\" slide --json | jq '.data.results[].format | select(.transition==\"morph\") | .transitionSpeed'` prints `\"slow\"` for each affected slide.\n\n## Outputs & delivery\n\nEvery morph deck ships with three artifacts, each as a standalone file:\n\n1. `<topic>.pptx` — the deck, closed + `officecli validate` clean (Delivery Gate 1 OK).\n2. `build.sh` or `build.py` — the re-runnable script (bash for shell-native builds; Python for multi-slide arcs using `morph-helpers.py`). Must recreate the deck from a fresh `officecli create` call.\n3. `brief.md` — **standalone file, NOT embedded in anything else.** Contains:\n   - Section 1: topic / audience / purpose / narrative / style direction (1 named style from `reference/styles/INDEX.md`)\n   - Section 2: slide-by-slide outline (page type + one-sentence argument per slide)\n   - Section 3: §Morph Pair Planning table (Pair / Slide A / Slide B / Actors / Ghosts) — the design record the reviewer needs to audit choreography\n\n**Pre-deliver reminder to the user (verbatim-safe wording):**\n\n- \"The deck is ready with morph transitions. Open it in PowerPoint 365 / Keynote / WPS to see the motion — LibreOffice and web viewers render static.\"\n- \"While the build script is running, the `.pptx` may be rewritten several times. If you want to preview progress, use `officecli watch \"$FILE\"` and open the live preview in AionUi — do NOT click 'Open with system app' during the build, or you'll hit a file lock.\"\n\n## Adjustments after creation\n\nStandard adjustments table → see pptx v2 §Common Pitfalls / `swap` / `move` / `remove` / `set`. Morph caveat: **after any `swap` or `move` that reorders morph-paired slides, re-verify the adjacency of shared `!!` names.** Run Gate 5b-morph-3 query above on the affected pairs — if the swap broke a pair, either rename shapes or re-choreograph the transition.\n\n**Final sanity check before delivery.** Run the full Delivery Gate (1 through 5b-morph-1..4), open the `.pptx` in PowerPoint 365 / Keynote / WPS, watch one full slide-to-slide morph to confirm motion is visible. If any Gate prints REJECT, fix and re-run — never deliver with a known-open gate.\n\n## References\n\n- `reference/decision-rules.md` — Pyramid Principle, SCQA, page-type menu, `brief.md` schema. Read during §Morph Pair Planning to decide narrative arc before writing commands.\n- `reference/pptx-design.md` — residual design notes (Scene Actors mechanics, page-type table, choreography patterns). Canvas / fonts / colors live in pptx v2 — this file covers only the morph-unique material.\n- `reference/morph-helpers.py` — Cross-platform (Mac / Windows / Linux) Python helpers for clone + ghost + verify + final-check. Import as a library or call via CLI args. Preferred for 5+ slide arcs.\n- `reference/morph-helpers.sh` — Bash equivalent. Pick one per project; do not mix.\n- `reference/styles/INDEX.md` — 52-style visual library, grouped by palette (dark / light / warm / vivid / bw / mixed) and mood. Lookup workflow in §Style library lookup workflow above.\n- `skills/officecli-pptx/SKILL.md` — base pptx v2 rules (visual floor, grid, canonical palettes, chart-choice, connector canon, Delivery Gate 1–5a, Known Issues C-P-1..7, Shell escape 3-layer).\n","skills/officecli-pitch-deck/SKILL.md":"---\nname: officecli-pitch-deck\ndescription: \"Use this skill when the user is building a fundraising / investor pitch deck — seed, Series A / B / C, convertible note, SAFE round, strategic raise. Trigger on: 'pitch deck', 'investor deck', 'Series A deck', 'Series B deck', 'Series C deck', 'fundraising deck', 'seed pitch', 'VC deck', 'raising capital', 'term sheet presentation'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate). DO NOT invoke for a generic board review, sales deck, all-hands, or product launch — route those to officecli-pptx base.\"\n---\n\n# OfficeCLI Pitch Deck Skill\n\n**This skill is a scene layer on top of `officecli-pptx`.** Every pptx hard rule — visual delivery floor (title ≥ 36pt / body ≥ 18pt / title ≥ 2× body), 12-column grid on 33.87×19.05cm, 4 canonical palettes, chart-choice decision table, connector canon (`shape` / `from` / `to` / `tailEnd=triangle`), shell escape, resident + batch, Delivery Gate 1–5a — is inherited, not re-taught. This file adds only what **fundraising** needs on top: stage diagnosis (A / B / C), 5 赛道 arc templates, 10 key-slide recipes (cover / problem / solution / market / product / model / traction / team / financials / ask), pitch-specific numbers convention, a VC ship-check, and a pitch-specific fresh-eyes Gate 6.\n\nWhen the pptx base rules cover it, the text here says `→ see pptx v2 §X`. Read `skills/officecli-pptx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what a fundraising deck requires, not every command flag.** When a prop name, enum value, or preset is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help pptx                          # All pptx elements\nofficecli help pptx <element>                # Full schema (e.g. chart, shape, connector, picture)\nofficecli help pptx <element> --json         # Machine-readable\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help wins.** Every `--prop X=` in this file has been grep-verified against `officecli help pptx <element>` — if help adds / renames a prop in a later version, trust help.\n\n## Mental Model & Inheritance\n\n**Inherits pptx v2.** You should have read `skills/officecli-pptx/SKILL.md` first. This skill assumes you know how to: add slides + shapes + charts + connectors; address by `@name=` / `@id=`; quote paths; use `batch` heredocs; write `--prop tailEnd=triangle` on every flow connector; and run the 5-gate Delivery Gate. If any of those are unfamiliar, open a pptx v2 session before continuing.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see pptx v2 §Shell & Execution Discipline. Same rules verbatim — quote `[N]` paths, single-quote values containing `$` (including `$35M`, `$1.2B TAM` in a cover or ask slide), never hand-write `\\$ \\t \\n` in executable examples, one command at a time. Examples below use `$FILE` (`FILE=\"deck.pptx\"`).\n\n**Single-quote every shape text containing `$`.** `--prop text=\"Series B · $35M\"` (double quotes) is WRONG — zsh expands `$35M` → empty, deck renders `Series B · M` silently. `--prop text='Series B · $35M'` (single quotes) is right. This is the #1 pitch-deck shell-escape failure mode (`$35M`, `$18M ARR`, `$1.2B TAM` appear on cover/ask/financials/milestones). Gate 2 cannot detect a stripped `$35M` — no residue. Gate 2b catches common strip patterns; single-quoting PREVENTS them.\n\n## What \"pitch deck\" means here (identity)\n\nA pitch deck is a pptx with a **fundraising layer** on top: VC-oriented narrative arc, verifiable metrics, stage-appropriate data density, founder-credibility surface. Slides are consumed at ~3 seconds per slide in a live room — the pptx v2 rule. Pitch decks add a second constraint on top: **every slide carries one investable proposition**. If a slide is \"interesting background\" that doesn't move the ask forward, cut it. VCs will not. The base pptx rules still apply; pitch decks add six deltas:\n\n1. **Stage determines everything.** Series A / B / C each dictates slide count, narrative weight, which metrics are must-haves, and tolerance for unit-econ sophistication. A Series A deck with 6 pages of CAC/LTV math reads as over-packaged; a Series B deck missing unit econ reads as incomplete. Pick the stage first — everything downstream follows.\n2. **Narrative arc beats feature dump.** 10 essential slides in a fixed order: cover → problem → solution → market → product → model → traction → team → financials → ask. Out of order = VCs disengage.\n3. **Numbers are a contract.** TAM/SAM/SOM must be clean three-layer; CAC/LTV must have a payback line; ARR ≠ revenue; Use-of-Funds must be a four-bucket pie. Sloppy numbers = round dies.\n4. **Team slide carries prior companies.** Avatar grid alone reads as a student project. Add prior-company logos / names + one-line role. Without this, first-time founders look exactly like first-time founders.\n5. **Traction chart y-axis starts at 0.** A \"hockey stick\" starting at `y_min = 80% of current` is a visual lie — VCs who have seen 10,000 decks spot it in < 2 seconds.\n6. **The ask is a slide, not a footnote.** `$XX M` hero + four-bucket Use-of-Funds + runway length. \"We're raising some money\" is not an ask.\n\n### Reverse handoff — when to go BACK to pptx base\n\nStay in **pptx v2 base** for board reviews, all-hands, sales decks, product launches, training decks — anything not tied to raising capital. Use **this skill** only when: (a) the user mentions a specific round (seed / Series A / B / C) or a VC meeting, AND (b) the deck needs at least 4 of {problem, traction, team with credentials, Use-of-Funds, stage-appropriate unit econ, financial projections}.\n\nIf the user says \"fundraising deck\" but the context is a corporate BU quarterly ask, that is a board review. Route to pptx v2 Recipe (d) 10-slide blueprint. If the user says \"board review\" but the context is a small company raising a bridge round, route here.\n\n## Series A / B / C stage diagnosis (decision tool)\n\n**Read this before writing a single command.** Pick the row that matches the user's description — everything downstream (slide count, which metrics, which recipes, what the team slide must show) derives from this one call.\n\n| Stage | Revenue band | Team | Slide count | Dominant narrative (weight) | Must-have data | Common red flag |\n|---|---|---|---|---|---|---|\n| **Seed** | $0 – $1M ARR (often pre-rev) | 2 – 8 FTE | 10 – 12 | Problem (30%) + Solution (25%) + Team (15%) + Market (15%) + Traction (15%) | Founder-market fit story; 1 – 2 design-partner / pilot logos; top-down TAM ok | Over-claiming traction (10 customers = \"market proven\") |\n| **Series A** | $1 – $5M ARR | 10 – 25 FTE | 12 – 16 | Problem (20%) + Solution (20%) + **Market \"why now\"** (15%) + Product (15%) + Traction (20%) + Team (10%) | PMF proof (NRR > 110%, low churn), bottom-up TAM/SAM, pipeline / pilots converted | Bottom-up TAM feels fabricated; CAC not yet meaningful but shown anyway |\n| **Series B** | $5 – $30M ARR | 30 – 100 FTE | 18 – 22 | **Traction + Unit econ (30%)** + Market + Product + Team + Financials (ask) | ARR curve starting at 0; NRR, CAC, LTV, payback (< 18 mo ideal); cohort retention; logo wall | No unit-econ slide; CAC payback > 24mo without explanation; Use-of-Funds missing % |\n| **Series C** | $30M+ ARR | 100+ FTE | 20 – 24 | **Financials + Scale + Moat (40%)** + Market expansion + Team depth | Multi-year GAAP, rule-of-40, GM trajectory, international expansion plan, defensibility | No moat slide; revenue growth without margin story; team slide has no prior CEO / CFO |\n| **Bridge / SAFE** | any | any | 8 – 10 | **Specific bridge reason** + runway math + commitments | Prior round context; specific milestone the bridge funds; committed investor amount | Treating a bridge like a Series A — too many slides dilutes the ask |\n\n**Decision procedure.** From one or two user sentences (\"Series B, $18M ARR, 120 customers, $35M raise\"), pick exactly one stage row. All later choices in this skill reference your stage: which 赛道 template to pull, which recipes are mandatory vs optional, and which Delivery Gate 6 checks fire.\n\n**Corner cases.** Bridge rounds & convertibles between A → B are closer to A or B depending on whether the bridge milestone is \"finish PMF\" (A shape) or \"hit unit-econ target\" (B shape). \"Extension\" rounds at the same stage reuse the earlier stage's skeleton and add a one-slide \"progress since last round\" update.\n\n**Non-SaaS stage overrides.** The ARR / unit-econ shape of Series B fits SaaS. For other verticals, substitute revenue band + unit-econ equivalent + Gate 6.3 grep:\n\n| Vertical | Revenue \"band\" at Series B | \"Unit econ\" equivalent | Gate 6.3 substitute |\n|---|---|---|---|\n| **Bio / Clinical-stage** | pre-rev, 20–60 FTE | burn rate + runway to next milestone (IND / Ph1 readout / BLA) | `shape:contains(\"ORR\")` OR `contains(\"Pipeline\")` OR `contains(\"BLA\")` OR `contains(\"runway\")` ≥ 1 |\n| **Deep Tech / Frontier** | pre-rev or early pilot rev | technical milestones + TRL level + benchmark vs SoTA | `shape:contains(\"TRL\")` OR `contains(\"benchmark\")` ≥ 1 |\n| **Marketplace / Network** | GMV $10–100M | take rate + cohort retention + liquidity | `shape:contains(\"GMV\")` + `contains(\"take rate\")` ≥ 1 |\n| **Consumer hardware** | $2–15M revenue (shipped units) | contribution margin + repeat rate + blended CAC | `shape:contains(\"repeat\")` OR `contains(\"contribution\")` ≥ 1 |\n\nSubstitute the analogue grep when running Gate 6.3 on these verticals. False WARN on SaaS CAC/LTV = expected; real concern = vertical-specific analogue present. Bio Series B decks especially: burn + runway-to-milestone IS the \"unit econ\" story.\n\n## 赛道 arc templates (5 families)\n\n5 mainstream verticals. Each one has different slide weights because what VCs require as proof-of-concept differs. Pick the vertical row; the slide skeleton is a copy-able starting point. Slide counts assume the matching stage row above.\n\n### (1) B2B SaaS / Enterprise software\n\nCanonical arc — the template most of VC muscle memory is built on. Series B example (20 slides): cover · TL;DR · problem · problem evidence · solution · product loop · market TAM/SAM/SOM · **unit economics (CAC / LTV / payback / GM)** · ARR trajectory · retention cohort · logo wall · team · competitors · financials 4-year · ask. Must-have: unit-econ slide from Series A onward; logo wall from Series B onward.\n\n### (2) Consumer (B2C app / consumer hardware / D2C)\n\nNarrative-driven. Early-stage decks lean on **product-experience screenshots + founding story + \"why now\"** market timing; lighter on unit econ (which are usually weaker than SaaS). Series A example (14 slides): cover · hook (30-second product demo or 1-line vision) · problem (lived experience) · solution (product shots) · product-experience flow · \"why now\" market window · pre-order / crowdfunding / early-sales evidence · retention / engagement (DAU, D30) · market (top-down ok if bottom-up unreliable) · competitive positioning · founder story + team · press / endorsements · financials · ask. Must-have: product visuals on ≥ 3 slides; \"why now\" slide (window justification); engagement metric not just revenue.\n\n### (3) Deep Tech / Frontier tech (AI foundation models, quantum, climate hardware, robotics)\n\nTechnology credibility is the sell. Pre-revenue deep tech replaces \"traction\" with **technical milestones + defensibility**. Series B example (22 slides): cover · thesis (one-line \"what changes if this works\") · problem (current state of art) · solution (technical approach) · **technology architecture** · benchmarks vs SoTA · pipeline / TRL levels · market (long-tail) · business model · early commercial traction (pilots, LOIs) · IP / patents · team (usually PhD / ex-FAANG-research) · partners · financials · ask. Must-have: benchmark slide; IP slide; team slide dense with PhDs / prior-lab names.\n\n### (4) Marketplace / Network business (two-sided platform, social, commerce)\n\nLiquidity is the metric. Replace \"unit econ\" with **GMV + take rate + cohort retention + supply / demand balance**. Series A example (15 slides): cover · problem (friction in current supply-demand) · solution · product demo (both sides) · network effects diagram · early liquidity (first-week GMV, time-to-match) · cohort retention · geographic / category expansion plan · competitive positioning vs incumbents · take-rate model · team · financials · ask. Must-have: liquidity metric slide; cohort retention chart; network-effect diagram.\n\n### (5) Bio / Life sciences / Healthtech\n\nRegulatory pipeline IS the business. Replace \"product roadmap\" with **clinical pipeline + regulatory path + scientific evidence**. Series B example (22 slides): cover · unmet medical need · scientific rationale (mechanism of action) · preclinical / clinical data (ORR, safety, endpoints) · **pipeline chart** (candidates × stages × dates) · differentiation vs standard of care · IP / exclusivity · regulatory strategy (IND, BTD, fast-track) · market (prevalence × pricing) · commercial strategy (orphan / specialty / biosimilar) · partnerships / collaborations · team (CSO / CMO with prior FDA wins) · financials (burn to next milestone) · ask. Must-have: pipeline chart; clinical data slide; team slide with prior regulatory wins.\n\n**Cross-vertical rule.** You can mix elements across templates, but never drop a must-have from your primary vertical. A SaaS deck missing unit econ, a bio deck missing a pipeline chart, a marketplace deck missing a liquidity metric — each is an instant VC disqualification.\n\n## Slide Patterns (layout canon)\n\nPatterns are **layout geometry**; recipes below are **narrative intent**. A slide picks one pattern for its visual shape (6 canonical ones below) and one recipe for what it argues (cover / problem / traction / ...). Multiple recipes can share one pattern — Problem / Why-Now / Traction-callout all lean on the 3-stat row (C.2). Pick the pattern first, then fill it with recipe content.\n\n**Speaker notes rule.** Every content slide (non-cover, non-closing) MUST carry speaker notes via `officecli add \"$FILE\" /slide[N] --type notes --prop text='…'`. Missing notes = not shippable — inherits pptx v2 §Hard rules (H7). Run `officecli help pptx notes` to confirm prop names before building.\n\n**Pattern reuse discipline.** Never run the same pattern on two consecutive slides — even with different data, two identical geometries in a row read as a template loop. Alternate C.2 with C.4 or C.5b to break rhythm.\n\n**Vertical centering.** When a slide carries fewer elements than the pattern's maximum, nudge y-positions down 2–3cm to center the visual weight. Tables below assume full content.\n\n### C.1 Title / Cover (dark gradient)\n\n3–4 text shapes on a gradient fill. Slide 1 in every deck.\n\n```\n+----------------------------------+\n|                                  |\n|          TITLE (centered)        |\n|          tagline                 |\n|                                  |\n|   round · amount · date          |\n|  ________________________        |  <- thin brand band\n+----------------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 2cm | 5cm | 29.87cm | 4cm | serif bold, ≥ 36pt (44 typical) |\n| Tagline | 2cm | 10cm | 29.87cm | 2cm | sans 18–22pt |\n| Meta (round · $ · date) | 2cm | 13cm | 29.87cm | 1.5cm | sans 12–16pt |\n\n**Use this when** the slide is the first one (Cover recipe 1) — 3-second identity grab. Background is a 180° linear gradient between two dark palette shades (e.g. Professional Navy `1E2761 → 0D1F35`). If the title wraps to 2 lines, **add height (4cm → 5cm), never drop font below 36pt** — sub-36pt on a pitch cover reads as timid regardless of content. Transition: fade.\n\n### C.2 3-Stat callout row\n\nTitle + 3 big-number / label pairs across. The default for Problem / Why-Now / Traction-callout slides.\n\n```\n+----------------------------------+\n|  Title                           |\n|                                  |\n|   73%      12hr      $4.2B       |\n|   label    label     label       |\n|   source   source    source      |\n+----------------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 1.5cm | 1cm | 30.87cm | 3cm | serif bold ≥ 36pt |\n| Stat 1 number | 2cm | 5cm | 9cm | 4cm | serif bold 60–64pt |\n| Stat 1 label | 2cm | 9.5cm | 9cm | 2cm | sans ≥ 16pt (H4 floor) |\n| Stat 2 number / label | 12.5cm | (same) | 9cm | (same) | (same) |\n| Stat 3 number / label | 23cm | (same) | 9cm | (same) | (same) |\n\n**Use this when** you have 2–3 anchoring numbers and the story is \"three facts argue the point\" — Problem, Why-Now, Market-callout, single-row Traction. Labels ≥ 16pt is the H4 floor (sub-label exception); a number without a label reads as bravado, so never drop labels to 12–14pt to fit more text.\n\n### C.3 4-Stat callout row\n\nSame geometry as C.2 but 4 columns. Numbers 60pt, width 7cm each.\n\n```\n+-------------------------------------+\n|  Title                              |\n|                                     |\n|  73%   12hr   $9M   4.2x            |\n|  lbl   lbl    lbl   lbl             |\n+-------------------------------------+\n```\n\n| Element | X positions | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 1.5cm | 1cm | 30.87cm | 3cm | serif bold 36pt |\n| Stat numbers | 1.5 / 9.5 / 17.5 / 25.5cm | 5cm | 7cm | 4cm | serif bold 60pt |\n| Stat labels | (same X) | 9.5cm | 7cm | 2cm | sans ≥ 16pt |\n\n**Use this when** exactly 4 parallel metrics tell the story and 3 feels under-counted. Prefer C.2 if in doubt — 4 always feels tighter than 3, and wrap risk is real.\n\n> **Wrap warning.** At 60pt in 7cm width, dollar patterns with both `$` and `.` fail: `$9.4M` is 5 glyphs but the wide `$` and `.` in a serif bold make it wrap to 2 lines and destroy the callout. Safe dollar shapes at 60pt/7cm: `$9M`, `$96B`, `$4K` (3–4 chars). Non-dollar shapes: `340%`, `4.2x`, `12.3` safe up to 5 chars. Values ≥ 6 chars (`197min`, `3 Days`) will wrap — either (a) drop font to 44–48pt, (b) abbreviate (`197m`, `$9M`), or (c) shift to C.2 (9cm per stat). Single tokens only, no internal spaces.\n\n### C.4 Chart + Context (chart left, stats right)\n\nChart takes left 55%, 2–3 stacked callouts on the right. The default for Traction / Financials / Market-sizing-with-context.\n\n```\n+-------------------------------------+\n|  Title                              |\n|                                     |\n|  +---------------+   +--------+     |\n|  |               |   | Stat 1 |     |\n|  |    chart      |   +--------+     |\n|  |               |   | Stat 2 |     |\n|  +---------------+   +--------+     |\n+-------------------------------------+\n```\n\n| Element | X | Y | Width | Height |\n|---|---|---|---|---|\n| Title | 2cm | 1cm | 29.87cm | 3cm |\n| Chart | 2cm | 4cm | 17cm | 13cm |\n| Stats column | 21cm | 4cm+ | 11cm | 2.5cm number + 1.5cm label (~3.7cm per pair) |\n\nSub-labels ≥ 16pt (H4 floor). For 5 stats stacked, drop number size to 44pt; 6+ stats means pick a different pattern. Post-batch for column/bar charts: `officecli set \"$FILE\" \"/slide[N]/chart[1]\" --prop gap=80` to tighten bar spacing.\n\n**Use this when** one primary chart drives the story and 2–3 numeric anchors reinforce it — Traction (ARR curve + current ARR + YoY + NRR), Financials (4-year column chart + assumption callouts), Market (bar chart + SOM / CAGR / methodology).\n\n### C.5 Icon-in-circle grid (3-row vertical)\n\n3 vertical rows, each = circle icon on the left + title + 1-line description.\n\n```\n+---------------------------------------+\n|  Title                                |\n|                                       |\n|  (o)  Label one                       |\n|       description one                 |\n|                                       |\n|  (o)  Label two                       |\n|       description two                 |\n|                                       |\n|  (o)  Label three                     |\n|       description three               |\n+---------------------------------------+\n```\n\n| Element | X | Y positions | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Icon circle | 2cm | 4.5 / 8.5 / 12.5cm | 2.5cm | 2.5cm | ellipse, accent fill |\n| Label | 5.5cm | (icon Y + 0) | 25cm | 1.2cm | sans bold 18pt |\n| Description | 5.5cm | (icon Y + 1.3cm) | 25cm | 1.8cm | sans ≥ 16pt (H4 floor), muted |\n\n**Use this when** you have 3 short vertical points that benefit from a visual anchor per row — Solution mechanism, Value pillars, Product loop. Choose C.5b (2×2 grid) when items are parallel and you have exactly 4; choose a horizontal 5-across variant when icons should read side-by-side (e.g. 5-step process).\n\n### C.5b 2×2 Feature grid (4 parallel items)\n\n4 rounded cards, 2 columns × 2 rows. Use when you have exactly 4 parallel items (product pillars, service types, feature quadrants).\n\n```\n+-----------------------------+\n|  Title                      |\n|                             |\n|  +---------+  +---------+   |\n|  | (o) T1  |  | (o) T2  |   |\n|  | body    |  | body    |   |\n|  +---------+  +---------+   |\n|  +---------+  +---------+   |\n|  | (o) T3  |  | (o) T4  |   |\n|  | body    |  | body    |   |\n|  +---------+  +---------+   |\n+-----------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Slide title | 2cm | 1cm | 29.87cm | 2.5cm | serif bold 32pt |\n| Card 1 bg (top-left) | 1.5cm | 4cm | 14.5cm | 7cm | roundRect |\n| Card 2 bg (top-right) | 17.5cm | 4cm | 14.5cm | 7cm | roundRect |\n| Card 3 bg (bottom-left) | 1.5cm | 12cm | 14.5cm | 7cm | roundRect |\n| Card 4 bg (bottom-right) | 17.5cm | 12cm | 14.5cm | 7cm | roundRect |\n| Icon ellipse (each card) | card_x + 0.5cm | card_y + 0.5cm | 2cm | 2cm | — |\n| Card title (each) | card_x + 3.2cm | card_y + 0.6cm | 10.5cm | 1.8cm | sans bold 16pt |\n| Card body (each) | card_x + 0.5cm | card_y + 3cm | 13cm | 3.5cm | sans ≥ 16pt (H4 floor) |\n\n**Use this when** you have exactly 4 parallel items and the eye should land on each equally — 4 product pillars, 4 service tiers, 4 stakeholder types. 3 items feel lonely in a 2×2; 5+ items break the grid — go to a 3×2 (see pptx v2 §(d) grid math) or C.5 row pattern.\n\n> **Z-order canon (critical).** Each card's `roundRect` background must be added immediately before that card's icon / title / body shapes in the batch JSON — pptx paints in insertion order, so a background added after its text paints over and hides the text. When building with `officecli batch`, follow the per-card sequence `bg → ellipse → title → body` strictly. Pattern and z-order details → see pptx v2 §Recipe (c) z-order canon; reuse grid math from pptx v2 §(d) for non-2×2 counts.\n\n**Dark-background variant.** Change card fill from `F0F4F8` (light) to a lighter-dark shade like `1A2540` and bump body text to `FFFFFF` / `E8E8E8`. Palette variables (e.g. `$MUTED`) do NOT expand inside single-quoted heredocs — write the literal hex (`64748B`) in the JSON.\n\n---\n\n## Key-slide recipes (10 essentials)\n\nThe 10 slides every pitch deck carries. Each recipe below gives: **visual outcome** (what the slide looks like from 3m away) + **runnable block** (≤ 18 lines) + **QA one-liner**. All recipes inherit pptx v2 palettes, grid math, type hierarchy, and `--prop tailEnd=triangle` on every connector. Recipes reference the Slide Patterns above: Cover reuses C.1; Problem / Why-Now reuse C.2; Traction / Financials reuse C.4; Feature / pillar slides reuse C.5b. `$FILE` is your deck file.\n\n**Long-title wrap rule.** A 36pt+ title that wraps to 2 lines: add `height` (e.g. 2cm → 3.5cm) — never drop the font below 36pt. Titles < 36pt on a pitch deck read as timid regardless of content.\n\n> **Chart `series1.color=` on `add` works** (applies to every chart recipe below). Passing `--prop series1.color=` (or `series2.color=`, …) on chart `add` applies the series color and exits 0. Verify with a readback if you like: `officecli get \"$FILE\" \"/slide[N]/chart[1]/series[1]\" --json | jq '.data.results[0].format.color'`.\n\n### (1) Cover slide — company · tagline · round · date\n\n**Visual outcome.** Dark navy fill, centered 44pt company name, 20pt one-line tagline underneath, small 16pt meta line at the bottom with round + amount + date. Thin brand band at the very bottom (0.5cm high) in the accent color.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=BrandBand \\\n  --prop geometry=rect --prop fill=CADCFC \\\n  --prop x=0cm --prop y=18.5cm --prop width=33.87cm --prop height=0.55cm\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=CoverTitle --prop text=\"Acme DevOps\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=Tagline --prop text=\"Kubernetes observability, built for production at scale\" \\\n  --prop x=2cm --prop y=10.5cm --prop width=29.87cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=20 --prop color=CADCFC --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=CoverMeta --prop text='Series B · $35M · April 2026' \\\n  --prop x=2cm --prop y=15cm --prop width=29.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=16 --prop color=FFFFFF --prop align=center --prop fill=none\n```\n\n**QA.** Cover has 4 discrete elements (brand band + title + tagline + meta). 80%-whitespace covers fail the pptx \"cover ≥ 60% filled\" floor.\n\n**Consumer variant (3-second grab).** Consumer decks (B2C app / hardware / D2C) should add a single dominant motif — hero product shot, oversized company name (60–96pt), or symbolic mark (crescent moon / abstract geometric). Replace the 44pt title with an 80–96pt name + one motif shape (`--type shape --prop geometry=ellipse --prop fill=<accent>` for an abstract mark, or `picture` at ~40% of slide for a product hero). Keep tagline + round + date identical. SaaS / B2B may skip — the typographic-only cover is sufficient.\n\n### (2) Problem slide — industry pain in 1 sentence + 3 data cards\n\n**Visual outcome.** 36pt title stating the pain (not \"The Problem\"). Below, three equal-width data cards across the slide: each a giant number (40pt) + one-line qualifier (16pt) + source footnote (12pt gray).\n\nGrid math for 3 cards, 1.5cm margins, 0.76cm gap: `usable = 33.87 − 3 − 2·0.76 = 29.35`, `col_width = 29.35 / 3 = 9.78cm`. x-positions: `1.5 / 12.04 / 22.58`.\n\n```bash\nSLIDE=2  # second slide, after cover. Adjust from your build order.\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Kubernetes debugging burns 12 engineering hours / incident\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.5cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"PC1\",\"geometry\":\"roundRect\",\"fill\":\"F5F7FA\",\"x\":\"1.5cm\",\"y\":\"5cm\",\"width\":\"9.78cm\",\"height\":\"10cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"73%\",\"x\":\"1.5cm\",\"y\":\"6cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"of incidents take > 1 hour to diagnose\",\"x\":\"1.5cm\",\"y\":\"9.5cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Calibri\",\"size\":\"18\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Source: 2025 DORA Report\",\"x\":\"1.5cm\",\"y\":\"13cm\",\"width\":\"9.78cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"12\",\"italic\":\"true\",\"color\":\"666666\",\"align\":\"center\",\"fill\":\"none\"}}\n]\nEOF\n# Repeat the 4-block pattern at x=12.04cm and x=22.58cm for cards 2 and 3.\n```\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"Source\")'` returns ≥ 3 (every claim carries a source). If zero sources, VCs will not trust a single number.\n\n### (2b) Why Now slide — Consumer / Seed / early A must-have\n\n**Visual outcome.** 3 cards across: each = **trigger headline** (24pt bold) + **data point** (60pt number or date) + **one-line implication** (16pt) + **source footnote** (12pt gray). Reuse Problem grid math (`col=9.78cm`, x = `1.5 / 12.04 / 22.58`). §赛道 Consumer row 2 must-have; Seed / early A in any vertical benefits when \"market window\" IS the thesis.\n\n```bash\nSLIDE=3\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Why now: three converging triggers\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.5cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# Card 1 (x=1.5cm) — trigger / data / implication / source. Repeat at x=12.04cm and x=22.58cm.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"geometry\":\"roundRect\",\"fill\":\"F5F7FA\",\"x\":\"1.5cm\",\"y\":\"5cm\",\"width\":\"9.78cm\",\"height\":\"10cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"BOM cost\",\"x\":\"1.5cm\",\"y\":\"5.5cm\",\"width\":\"9.78cm\",\"height\":\"1.2cm\",\"font\":\"Calibri\",\"size\":\"24\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"−90%\",\"x\":\"1.5cm\",\"y\":\"7cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"B85042\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Wearable BOM fell 90% since 2021; sub-$40 retail now viable\",\"x\":\"1.5cm\",\"y\":\"11cm\",\"width\":\"9.78cm\",\"height\":\"2cm\",\"font\":\"Calibri\",\"size\":\"16\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Source: IDC Wearables Teardown 2025\",\"x\":\"1.5cm\",\"y\":\"13.5cm\",\"width\":\"9.78cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"12\",\"italic\":\"true\",\"color\":\"666666\",\"align\":\"center\",\"fill\":\"none\"}}\n]\nEOF\n# Card 2 pattern: Oura IPO 2024 / +$2.4B valuation / category proven. Card 3: On-device LLM (Llama 3.2) / Q4-24 / privacy moat viable.\n```\n\n**QA.** 3 cards, each with a date/year citation in the source footnote, each card ≤ 30 words. `officecli query \"$FILE\" 'shape:contains(\"2024\")'` + `'shape:contains(\"2025\")'` ≥ 2 combined (timing anchors visible).\n\n### (3) Solution slide — product in one sentence + 3-step \"how it works\"\n\n**Visual outcome.** 36pt title naming the product pattern (not \"Our Solution\"). Below: 3 or 4 rounded boxes horizontally at y=7cm with elbow connectors + triangle arrowheads. Each box = one verb (observe / correlate / resolve). Reuse pptx Recipe (c) flowchart — orchestration, not a new primitive.\n\n```bash\n# Title — \"a product pattern, not a brand slogan\".\n# Good: \"Auto-correlate K8s events across 3 data planes in 90 seconds\"\n# Bad:  \"The future of observability\"\nSLIDE=4\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop name=SolTitle \\\n  --prop text=\"Correlate K8s events across 3 data planes in 90 seconds\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.2cm \\\n  --prop font=Georgia --prop size=32 --prop bold=true --prop color=1E2761 --prop fill=none\n# 3 boxes across: gap = (33.87 − 3 − 3·7) / 2 = 4.93cm; x = 1.5, 13.43, 25.36\n# Connectors + arrowheads: --prop tailEnd=triangle ALWAYS (pptx Known Issues C-P-5..6).\n# Full batch block → see pptx v2 §Creating and Editing (c) 4-step flowchart; swap N from 4 boxes to 3.\n```\n\n**Product-pattern title rule.** The solution title is a verb + differentiated mechanism + metric. \"Observe / Correlate / Resolve\" is generic; VCs read it as any APM vendor. \"Correlate K8s events across 3 data planes in 90 seconds\" is specific; VCs read it as an insight.\n\n**QA.** Count connectors: `officecli query \"$FILE\" 'connector' --json | jq '.data.results | length'` ≥ (step_count − 1). Every connector must have `tailEnd=triangle` — `view annotated` confirms arrowhead direction. Title must be ≤ 12 words (one breath).\n\n### (4) Market slide — TAM / SAM / SOM nested columns\n\n**Visual outcome.** 36pt title \"Market: $X.YB growing Z% CAGR\". Below: three horizontal bars (or three stacked nested rectangles), labeled TAM / SAM / SOM with dollar values + growth rate. Bottom footnote cites **top-down vs bottom-up source** — pick one methodology per deck, don't mix.\n\n```bash\n# Use a pptx column chart with 3 values. Categories = TAM,SAM,SOM. Source annotation is a separate shape.\nSLIDE=5\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"$42B observability market, 18% CAGR\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=bar \\\n  --prop series1.name=\"USD (billions)\" --prop series1.values=\"42,8.4,0.62\" --prop series1.color=1E2761 \\\n  --prop categories=\"TAM,SAM,SOM (5-yr)\" \\\n  --prop x=2cm --prop y=4cm --prop width=22cm --prop height=12cm \\\n  --prop title='Market sizing — bottom-up by enterprise count × ACV'\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='Source: Gartner 2025 APM Magic Quadrant; SAM = 20% of TAM (K8s-first shops); SOM = 7.4% of SAM over 5 years at 18-24% share.' \\\n  --prop x=2cm --prop y=16.5cm --prop width=29.87cm --prop height=2cm \\\n  --prop font=Calibri --prop size=12 --prop italic=true --prop color=666666 --prop fill=none\n```\n\n**QA.** Top-down vs bottom-up MUST be declared in the source footnote. A TAM without methodology reads as fabricated.\n\n### (5) Product slide — screenshot + 3 bullets OR 3-card feature grid\n\n**Visual outcome.** Two layout options: (a) hero product screenshot on the left (60% of slide), 3 one-line feature bullets on the right (each ≥ 18pt body, no bullets under bullets). (b) 3 feature cards with one icon / screenshot thumbnail each. Pick (a) for consumer / app products, (b) for B2B / infrastructure.\n\n```bash\n# (a) screenshot + bullets — consumer pattern\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type picture --prop src=product_hero.png \\\n  --prop x=1cm --prop y=4cm --prop width=18cm --prop height=13cm\nofficecli set \"$FILE\" \"/slide[$SLIDE]/picture[1]\" --prop alt=\"Product UI: dashboard with 12 K8s clusters, live correlation graph\"\n# Right column bullets (each as a separate shape so sizes stay explicit)\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Auto-correlate across 3 data planes\" \\\n  --prop x=20cm --prop y=5cm --prop width=12cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=20 --prop bold=true --prop color=1E2761 --prop fill=none\n# Repeat for bullets 2 and 3 at y=7.5cm / y=10cm.\n```\n\n**QA.** Picture alt text present (`query 'picture:no-alt'` = empty). Bullets each ≥ 18pt. No \"Lorem\"/\"product name here\"/`{{...}}` tokens.\n\n### (6) Business model slide — unit econ or revenue model\n\n**Visual outcome.** Decision tree by vertical:\n- **SaaS / Enterprise (Series A+)** — 4 KPI callouts: CAC / LTV / Payback / GM (reuse pptx Recipe (e)).\n- **Consumer / D2C** — AOV · repeat-purchase rate · contribution margin · blended CAC.\n- **Marketplace** — GMV / take-rate / liquidity metric / cohort retention.\n- **Bio / Deep tech** — revenue model (license / milestone / royalty split) with assumed ranges.\n\nTitle names the dominant metric (e.g. \"LTV:CAC 4.7x · 14-month payback · 78% gross margin\"), not \"Business Model\". Full 4-card batch block → see pptx v2 §(e) KPI callouts.\n\n```bash\n# SaaS pattern: KPI card values + sub-label + gray VC-floor context under each.\n# Card 1 (LTV): big number \"$420K\", sub \"Lifetime value\", context \"floor: ARPU × GM / churn\"\n# Card 2 (CAC): big number \"$90K\",  sub \"Acquisition cost\", context \"fully-loaded S&M spend\"\n# Card 3 (Payback): big number \"14 mo\", sub \"CAC payback\", context \"VC floor: < 18 mo\"\n# Card 4 (GM): big number \"78%\", sub \"Gross margin\", context \"SaaS floor: 70%+\"\n# Grid math for 4 cards across: usable = 33.87 − 3 − 3·0.76 = 28.59, col = 7.15cm\n# → Full batch template → pptx v2 §(e). Adapt card count 3→4 and card width 9.78cm→7.15cm.\n```\n\n**QA.** For Series B+, all four of {CAC, LTV, payback, GM} present: `officecli query \"$FILE\" 'shape:contains(\"CAC\")'` ≥ 1 AND `shape:contains(\"LTV\")'` ≥ 1 AND `shape:contains(\"payback\")'` ≥ 1 AND `shape:contains(\"gross margin\")'` ≥ 1.\n\n### (7) Traction slide — ARR curve that starts at 0\n\n**Visual outcome.** Line chart taking 60% of slide width; ARR on y-axis **starting at 0** (not at 80% of current value — the VC hockey-stick lie). Right-side commentary card: single giant number (current ARR) + growth rate + 2-3 milestones. If Series B+, second row: cohort retention snippet or logo wall.\n\n```bash\nSLIDE=7\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='ARR: $0 → $18M in 24 months' \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=line \\\n  --prop series1.name=ARR --prop series1.values=\"0.2,0.6,1.4,3.2,6.1,11.3,15.8,18.0\" --prop series1.color=1E2761 \\\n  --prop categories=\"Q1-24,Q2-24,Q3-24,Q4-24,Q1-25,Q2-25,Q3-25,Q4-25\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=21cm --prop height=13cm \\\n  --prop title='Quarterly ARR ($M) — y-axis anchored at 0' \\\n  --prop axismin=0\n# Right callout\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop geometry=roundRect --prop fill=1E2761 --prop line=none \\\n  --prop x=23.5cm --prop y=4cm --prop width=8.8cm --prop height=13cm\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$18M' \\\n  --prop x=23.5cm --prop y=5cm --prop width=8.8cm --prop height=3cm \\\n  --prop font=Georgia --prop size=64 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"ARR · +312% YoY · NRR 128%\" \\\n  --prop x=23.5cm --prop y=9cm --prop width=8.8cm --prop height=3cm \\\n  --prop font=Calibri --prop size=18 --prop color=CADCFC --prop align=center --prop fill=none\n```\n\n**`--prop axismin=0` is load-bearing** — without it, pptx auto-scales the y-axis to start near the lowest value. That is the hockey-stick lie. Gate 6 greps this below.\n\n**QA.** ARR curve chart must carry `axismin=0`. `officecli get \"$FILE\" \"/slide[$SLIDE]/chart[1]\" --json | jq .format.axisMin` returns `0` (CLI emits camelCase `axisMin` in readback even though input prop is lowercase `axismin`).\n\n### (8) Team slide — avatars + names + prior companies (not just a wall)\n\n**Visual outcome.** 3- or 4-card row across the middle of the slide. Each card: picture (6×6cm) on top; name (20pt bold); role (16pt); **prior company + title** (16pt italic, 1 key line); optional LinkedIn URL footer (12pt). Team slide with just headshots and names reads as amateur.\n\n```bash\nSLIDE=11\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Team: 3 prior exits, 42 years combined K8s\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# Card 1 — CEO\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type picture --prop src=alice.jpg \\\n  --prop x=2cm --prop y=5cm --prop width=6cm --prop height=6cm\nofficecli set \"$FILE\" \"/slide[$SLIDE]/picture[1]\" --prop alt=\"Alice Chen, CEO — portrait\"\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Alice Chen\" \\\n  --prop x=2cm --prop y=11.5cm --prop width=6cm --prop height=1cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761 --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"CEO\" \\\n  --prop x=2cm --prop y=12.8cm --prop width=6cm --prop height=0.8cm \\\n  --prop font=Calibri --prop size=16 --prop color=333333 --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"ex-Datadog Director (Series C → IPO); led K8s observability GTM $40M → $200M ARR\" \\\n  --prop x=2cm --prop y=13.8cm --prop width=6cm --prop height=2.5cm \\\n  --prop font=Calibri --prop size=14 --prop italic=true --prop color=333333 --prop align=center --prop fill=none\n# Repeat for Card 2 (CTO, x=10cm) and Card 3 (VP Eng, x=18cm) — 3 cards × 5-6 shapes each.\n```\n\nPrior companies carry **credibility density**. VCs read \"ex-Datadog Director + led $40M → $200M\" in 2 seconds; they read \"co-founder, passionate\" in 0 seconds (because they skip it). Advisors, if shown, go in a smaller row below with a single logo each.\n\n**Arrangement helper.** 3 cards: `col=9.78cm, x=1.5/12.04/22.58`. 4 cards: `col=7.15cm, x=1.5/9.41/17.32/25.23`. 5 cards: `col=5.85cm, x=1.5/7.75/14.0/20.25/26.5` (0.4cm gap, tighter). 6+ or asymmetric → 2-row grid (3×2 / 3×3); see pptx v2 §(d) grid math.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"ex-\")'` + `'shape:contains(\"prior\")'` + `'shape:contains(\"former\")'` ≥ 1 per team member. If zero, you have a portfolio, not a team.\n\n### (9) Financials slide — 4-year plan + honest assumptions\n\n**Visual outcome.** Column chart: 4 years × (revenue, gross margin $, EBITDA). Right-side card: 3-bullet assumption panel (ARPU assumption, win-rate assumption, churn assumption). Title names the trajectory (\"$18M → $85M by FY29\"), not \"Financial Projections\".\n\nReuse pptx Recipe (b) chart + commentary. Pitch-specific: ASSUMPTIONS column on the right is **load-bearing** — a 4-year plan without visible assumptions reads as aspirational. VCs will ask what's behind every number anyway; surface it.\n\nLeft 2/3 — slide + title + 3-series column chart:\n\n```bash\nSLIDE=17\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$18M → $85M ARR by FY29' \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=column \\\n  --prop series1.name=\"Revenue ($M)\"  --prop series1.values=\"18,34,58,85\" --prop series1.color=1E2761 \\\n  --prop series2.name=\"Gross Margin ($M)\" --prop series2.values=\"14,26,45,68\" --prop series2.color=CADCFC \\\n  --prop series3.name=\"EBITDA ($M)\"   --prop series3.values=\"-6,-2,8,22\" --prop series3.color=B85042 \\\n  --prop categories=\"FY26,FY27,FY28,FY29\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=20cm --prop height=13cm \\\n  --prop title='4-year plan — revenue, GM, EBITDA ($M)'\n```\n\nRight 1/3 — assumptions commentary card:\n\n```bash\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop geometry=roundRect --prop fill=F5F7FA --prop line=none \\\n  --prop x=22.5cm --prop y=4cm --prop width=9.8cm --prop height=13cm\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Key Assumptions\" \\\n  --prop x=23cm --prop y=4.5cm --prop width=8.8cm --prop height=1.2cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761 --prop fill=none\n# 5 assumption bullets as 5 separate paragraph shapes at y=6, 7.5, 9, 10.5, 12cm — size=14, italic=true.\n# Keep each bullet ≤ 14 words so 8.8cm width fits without wrap.\n```\n\n**Assumptions panel is load-bearing.** A 4-year plan without visible assumptions reads as aspirational. VCs ask what's behind every number anyway — surface the three or four assumptions that drive the curve.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"assumption\")'` OR `'shape:contains(\"Assumes\")'` ≥ 1. If zero, add the panel.\n\n### (10) The Ask — hero number + 4-bucket Use-of-Funds + runway\n\n**Visual outcome.** Dark fill (match cover). Hero number in the center top: `$35M` at 96pt white. Below, a 4-bucket pie OR a 4-card row listing **Engineering 40% / GTM 35% / G&A 15% / Reserve 10%**. Bottom line: \"18-month runway to $40M ARR\" (next milestone, not \"until next round\").\n\n```bash\nSLIDE=20\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$35M Series B' \\\n  --prop x=2cm --prop y=2cm --prop width=29.87cm --prop height=4cm \\\n  --prop font=Georgia --prop size=88 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=pie \\\n  --prop series1.name=\"Use of Funds\" --prop series1.values=\"40,35,15,10\" \\\n  --prop categories=\"Engineering,Go-to-Market,G&A,Reserve\" \\\n  --prop colors=\"CADCFC,B85042,97BC62,FFFFFF\" \\\n  --prop x=6cm --prop y=7cm --prop width=12cm --prop height=10cm \\\n  --prop title=\"Use of Funds — 4 buckets\"\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='18 months runway to $40M ARR and Series C' \\\n  --prop x=2cm --prop y=17cm --prop width=29.87cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=22 --prop color=CADCFC --prop align=center --prop fill=none\n```\n\n**4-bucket convention.** Engineering / GTM / G&A / Reserve is the canonical breakdown. Typical Series A ranges: Eng 40-50%, GTM 30-40%, G&A 10-15%, Reserve 5-10%. Series B shifts 5-10 points from Eng to GTM.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"Use of Funds\")'` ≥ 1. Pie chart present on ask slide. Runway + milestone on ask slide.\n\n### (11) Pipeline chart — Bio / Deep Tech must-have\n\n**Visual outcome.** Horizontal swimlane. Left column = candidate name; 4 stage columns to the right (Preclinical / Ph1 / Ph2 / Ph3 for bio — or TRL1-3 / TRL4-6 / TRL7-8 / TRL9 for deep tech). Each row's bar extends to its current stage; darker fill for later stages. NCT / trial-ID footer below. §赛道 row 5 Bio must-have; SaaS / Consumer skip.\n\nGrid math: usable `= 30.87cm`, candidate col `= 7cm`, stage cols `= (30.87 − 7) / 4 = 5.97cm` each, row height `= 2.3cm`. Stage col x: `8.5 / 14.47 / 20.44 / 26.41`.\n\n```bash\nSLIDE=6\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Pipeline: 3 candidates across Ph1–Ph3\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# 4 stage headers + candidate row 1 (HLX-201 at Ph2, bar width = 3·5.97 = 17.91cm) in one batch.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Preclinical\",\"x\":\"8.5cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 1\",\"x\":\"14.47cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 2\",\"x\":\"20.44cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 3\",\"x\":\"26.41cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"HLX-201 (lead)\",\"x\":\"1.5cm\",\"y\":\"5.5cm\",\"width\":\"7cm\",\"height\":\"1.5cm\",\"font\":\"Calibri\",\"size\":\"18\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"left\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"geometry\":\"roundRect\",\"fill\":\"1E2761\",\"x\":\"8.5cm\",\"y\":\"5.7cm\",\"width\":\"17.91cm\",\"height\":\"1.1cm\",\"line\":\"none\"}}\n]\nEOF\n# Repeat rows 2 & 3 at y=7.8cm / y=10.1cm with bar widths per stage (Ph1=5.97cm, Ph1-Ph2=11.94cm, Ph1-Ph3=17.91cm).\n# NCT footer full-width at y=16.8cm.\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='NCT05021323 (HLX-201, Ph2, n=48) · NCT06142091 (HLX-304, Ph1, n=24) · IND-filed Q1-26 for HLX-412' \\\n  --prop x=1.5cm --prop y=16.8cm --prop width=30.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=12 --prop italic=true --prop color=666666 --prop fill=none\n```\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"NCT\")' --json | jq '.data.results | length'` ≥ 1. Bar colors darken across stages (`CADCFC` preclinical-only, `1E2761` Ph2-reached).\n\n### (12) Competitive comparison table — Series B+ essential\n\n**Visual outcome.** 5–7 rows × 4–6 cols. Column 1 = competitor name (optional logo shape beside); rest = differentiators (speed / price / integrations / margin / coverage). **Last row = your company, fill highlighted** in an accent color (CADCFC / 97BC62); competitor rows gray. Every Series B+ deck needs this (SaaS: Datadog / New Relic / Splunk; Bio: Kite / Novartis / BMS).\n\n```bash\nSLIDE=13\nofficecli add \"$FILE\" / --type slide --prop layout=blank --pro","skills/officecli-xlsx/SKILL.md":"---\nname: officecli-xlsx\ndescription: \"Use this skill any time a .xlsx file is involved -- as input, output, or both. This includes: creating spreadsheets, financial models, dashboards, or trackers; reading, parsing, or extracting data from any .xlsx file; editing, modifying, or updating existing workbooks; working with formulas, charts, pivot tables, or templates; importing CSV/TSV data into Excel format. Trigger whenever the user mentions 'spreadsheet', 'workbook', 'Excel', 'financial model', 'tracker', 'dashboard', or references a .xlsx/.csv filename.\"\n---\n\n# OfficeCLI XLSX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good xlsx looks like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help xlsx                         # List all xlsx elements\nofficecli help xlsx <element>               # Full element schema (e.g. pivottable, chart, cf)\nofficecli help xlsx <verb> <element>        # Verb-scoped (e.g. add chart, set cell)\nofficecli help xlsx <element> --json        # Machine-readable schema\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help is authoritative**.\n\n## Shell & Execution Discipline\n\n**Shell quoting (zsh / bash).** Excel paths contain `[]`, and number formats contain `$`. Both are shell metacharacters. Rules:\n\n- ALWAYS quote element paths: `\"/Sheet1/row[1]\"`, not `/Sheet1/row[1]`.\n- Use **single quotes** for any prop value containing `$`: `numFmt='$#,##0'`.\n- For formulas with cross-sheet `!` references, use `batch` with a `<<'EOF'` heredoc (see Known Issues).\n- `\\n` and `\\t` in a prop value ARE interpreted by the CLI — `\\n` is a real in-cell line break (pair with `--prop wrapText=true`), `\\t` a tab — consistent across xlsx / docx / pptx. Double them (`\\\\n`) for a literal backslash-n (rarely wanted). (`$` is the shell layer above — single-quote it.)\n\n**Incremental execution.** Run commands one at a time and read each exit code. `officecli` mutates the file on every call; a 50-command script that fails at command 3 will cascade silently. One command → check output → continue.\n\n## Requirements for Outputs\n\nBefore reaching for a command, know what a good xlsx looks like. These are the deliverable standards every workbook MUST meet.\n\n### All Excel files\n\n**Zero formula errors.** Every delivered workbook MUST have ZERO `#REF!`, `#DIV/0!`, `#VALUE!`, `#NAME?`, `#N/A`. No exceptions — guard denominators with `IFERROR` or `IF(x=0,...)`.\n\n**Formulas, not hardcoded values.** If a number can be computed from other cells, it is a formula. Hardcoding `5000` where `=SUM(B2:B9)` belongs breaks the contract that the workbook stays live when inputs change. This is the single most important rule in this skill.\n\n**Professional font.** Use one consistent, professional font across the workbook (Arial / Calibri / Times New Roman). Don't mix four fonts because one sheet came from CSV.\n\n**Explicit widths.** There is no auto-fit. Any column the user will read MUST have `width` set — default 8.43 chars clips everything. Sensible starts: labels 20-25, numbers 12-15, dates 12, short codes 8-10.\n\n**Preserve existing templates.** When editing a file that already has a look, match it. Existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY workbook)\n\nBefore you declare done, run `officecli view \"$FILE\" html` and Read the returned HTML path to confirm all of these:\n\n- **No `###` in any cell.** `###` means a column is too narrow for its widest value. Every column the user reads needs an explicit `width`. `###` in a delivered file is unfinished work, never \"a small visual nit\".\n- **No truncated titles.** Sheet titles, section headers, long labels must fit. Widen the column or apply `wrapText=true` on the cell.\n- **No placeholder tokens rendered as data.** `$fy$24`, `{var}`, `<TODO>`, `xxxx` must never appear in a cell, chart title, series name, or legend. These are build-time tokens that escaped replacement.\n- **Pie / doughnut slices have distinct fill colors.** If the slices render same-colored, switch to `bar` / `column` or set `colors=...` explicitly.\n- **No empty trailing pages / empty chart anchors.** `anchor=D2:J18` over empty source cells looks like a broken chart.\n\nIf any of the above fails, STOP and fix before declaring done.\n\n**Print layout.** Any sheet the user may print or send as a board pack needs page setup. Default portrait + no fit-to-page splits wide tables and charts mid-way. Pick the fit mode by sheet shape:\n\n```bash\n# Summary / chart / dashboard sheet (small, ≤ ~40 rows): fit to a single page.\nofficecli set \"$FILE\" \"/Summary\" --prop orientation=landscape --prop fitToPage=true\n# Tall data table (dozens+ rows): fit WIDTH only, let height paginate naturally.\n# fitToPage=true here crushes every row onto one page → unreadable (### dates, 5px rows).\nofficecli set \"$FILE\" \"/Data\" --prop orientation=landscape --prop fitToPage=1x0\n```\n\n`fitToPage=true` == `1x1` == fit both axes to one page — correct only when the sheet is already short. `1x0` = fit 1 page wide, unlimited pages tall. Trigger: sheet holds a chart, or > 8 columns, or the user's ask mentions print / board / investor.\n\n### Financial models only — skip this section if you are building a template, tracker, CSV import, or operational sheet\n\nScope: budgets, forecasts, 3-statement models, valuation, any `$`-heavy analytical workbook. A customer-support tracker or onboarding template does not need this section.\n\n**Color coding — industry standard.** Five core colors used as a language, not decoration. A reviewer should tell what a cell IS by color alone — before reading the formula.\n\n| Color | Role | Example |\n|---|---|---|\n| Blue text `0000FF` | Hardcoded inputs, scenario variables | `font.color=0000FF` |\n| Black text `000000` | ALL formulas and calculations | default |\n| Green text `008000` | Cross-sheet links inside this workbook | `font.color=008000` |\n| Red text `FF0000` | Links to external files / workbooks | `font.color=FF0000` |\n| Yellow fill `FFFF00` | Key assumptions needing review | `fill=FFFF00` |\n\nA reviewer should tell what a cell IS just by its color — before reading the formula. This is a communication contract, not a cosmetic preference.\n\n**Number formatting — standards, not preferences.**\n\n- **Years** are text, not numbers. Format `2026` not `2,026` — use `numFmt=\"@\"` or set `type=string`.\n- **Currency** carries its unit in the header (`Revenue ($mm)`), not in every cell.\n- **Zeros display as `-`**, not `0`. Use `$#,##0;($#,##0);\"-\"`.\n- **Percentages** default to one decimal: `0.0%`.\n- **Negatives use parentheses**: `(1,234)` not `-1,234`.\n- **Valuation multiples** use `0.0x` format (EV/EBITDA, P/E, etc.).\n\n**Assumptions live in cells, not inside formulas.** `=B5*(1+$B$6)` is correct; `=B5*1.05` is a bug. Document each blue hardcoded input with an adjacent source note in the next cell or a cell comment:\n\n```\nSource: Company 10-K, FY2024, Page 45, Revenue Note\nSource: Bloomberg, 2026-05-02, AAPL US Equity\nSource: Management guidance, Q2 2026 earnings call\n```\n\nAny hardcoded number without a source is an undocumented assumption — a reviewer cannot audit it.\n\n## Common Workflow\n\nSix steps. Every non-trivial build follows this shape.\n\n1. **Open/save lifecycle.** Use `officecli open <file>` at the start and `officecli save <file>` at the end to flush to disk — `save` only writes and leaves the resident warm for follow-up edits; reach for `officecli close <file>` only to release the resident on a one-shot handoff. Both are always safe (never error or lose work). For many cells, use `batch`: **≤ 50 ops/block recommended; tested up to 80+ ops per block on pure value-set payloads with zero failures. Cross-sheet formula batches are the exception — run those non-resident, single heredoc (see Known Issues)**. **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (openpyxl/pandas, Excel, a renderer, delivery).\n2. **Create or load.** `officecli create \"$FILE\"` (new) or `officecli view \"$FILE\" outline` (existing — get the lay of the land first).\n3. **Build incrementally.** One command, read the output, continue. After any structural op (new sheet, chart, named range, pivot), run `get` on it to confirm shape before stacking more on top.\n4. **Format.** Column widths, number formats, freeze panes, tab colors, header fills. Formatting is not optional polish — per \"Requirements for Outputs\" it is part of the deliverable.\n5. **Save, then reckon with the cache.** `officecli save <file>` writes to disk. Newly-added formulas ship without cached values; when a human opens the file in a spreadsheet app, the app recalculates and populates them. **But your downstream `INDEX/MATCH`, `SUMPRODUCT`, or any formula that references an upstream formula will cache whatever the upstream cached at write-time — often `0` or a stale value — and that cached lie survives into non-recalculating readers.** After any multi-formula build involving array formulas (`SUMPRODUCT`, `SUMIFS` with dynamic criteria) or cross-sheet chains, **re-touch every downstream cell** (run `set` again with the same formula) so the engine recomputes its cache from the freshly-cached upstream. ⚠️ Re-touch on cross-sheet chains via resident is unreliable (see Batch / resident caveats) — prefer non-resident `set` for the re-touch pass. Then `officecli get` a few downstream cells and eyeball that their `cachedValue=` is plausible. `validate` is safe with a resident open and itself flushes pending edits to disk (same as docx / pptx).\n6. **QA — assume there are problems.** See the QA section. You are not done when your last command exited 0; you are done after one fix-and-verify cycle finds zero new issues.\n\n## Quick Start\n\nMinimal viable xlsx: 3 months of revenue + a total formula + column widths + a currency format. Adapt, don't copy-paste — your file, your data.\n\n```bash\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" /Sheet1/A1 --prop value=Month --prop bold=true\nofficecli set \"$FILE\" /Sheet1/B1 --prop value=Revenue --prop bold=true\nofficecli set \"$FILE\" /Sheet1/A2 --prop value=Jan\nofficecli set \"$FILE\" /Sheet1/A3 --prop value=Feb\nofficecli set \"$FILE\" /Sheet1/A4 --prop value=Mar\nofficecli set \"$FILE\" /Sheet1/B2 --prop value=42000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/B3 --prop value=45000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/B4 --prop value=48000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/A5 --prop value=Total --prop bold=true\nofficecli set \"$FILE\" /Sheet1/B5 --prop formula=\"SUM(B2:B4)\" --prop bold=true --prop numFmt='$#,##0'\nofficecli set \"$FILE\" \"/Sheet1/col[A]\" --prop width=12\nofficecli set \"$FILE\" \"/Sheet1/col[B]\" --prop width=15\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified: `validate` returns `no errors found`, `B5` resolves to `135000`. This is the shape of every build: open → set cells/formulas → format → close → validate.\n\n## CSV / bulk import\n\n**Native `import` command (preferred for CSV/TSV).** Fastest path; loads a CSV into a sheet in one call. `--header` sets AutoFilter + freeze pane on row 1. Widths and `numFmt` still need a follow-up pass (per D-12 in Dashboard skill).\n\n```bash\nofficecli import \"$FILE\" /Sheet1 --file data.csv --header\nofficecli import \"$FILE\" /Sheet1 --file data.tsv --format tsv --header\nofficecli import \"$FILE\" /Sheet1 --stdin --start-cell B2 < data.csv\n```\n\n**Python + batch fallback** — use when you need custom type coercion, formula injection, or the CSV lives inside another data pipeline. Recipe for 600-6000+ cells:\n\n```python\n# gen_batch.py — produces batch chunks of 80 value-set ops each\nimport csv, json\nops = []\nwith open(\"data.csv\") as f:\n    reader = csv.reader(f)\n    for r, row in enumerate(reader, start=1):\n        for c, val in enumerate(row):\n            col = chr(ord('A') + c)\n            ops.append({\"command\":\"set\",\"path\":f\"/Data/{col}{r}\",\n                        \"props\":{\"value\": val}})\nfor i in range(0, len(ops), 80):\n    print(json.dumps(ops[i:i+80]))\n```\n\n```bash\npython gen_batch.py | while IFS= read -r chunk; do\n  printf '%s\\n' \"$chunk\" | officecli batch \"$FILE\"\ndone\n```\n\nOutcome: 648-row retail CSV (6490 cells) loads in ~30s, zero failures. Tune: start at 80 ops/chunk, drop to 40 if any chunk fails. Numeric type inference and formulas come later via targeted `set` — batch in this recipe is pure value injection.\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` first tells you what sheets exist and where the data is; jump into `view` / `get` / `query` only once you know where to look.\n\n**Open the rendered workbook to eyeball your own work.**\n- `officecli view $FILE html` — Read the returned HTML to audit the rendered output. Each sheet is addressable, charts render inline. Catches `###`, placeholder leakage, pivot layout, row-height clipping.\n- `officecli watch $FILE` keeps a live preview running for the human user — they open it at their own discretion. Use when the user wants to watch along; agent self-check uses `view html` above.\nUse `view html` as your **first visual check after a batch of edits** — fix at source. For final visual verification, the user opens the `.xlsx` in their Excel / WPS / Numbers viewer.\n\n**Orient.** Sheets, dimensions, formula counts.\n\n```bash\nofficecli view \"$FILE\" outline\n```\n\n**Extract.** Plain text dump for content QA or LLM context; scope with `--start` / `--end` / `--cols` for big files.\n\n```bash\nofficecli view \"$FILE\" text --start 1 --end 50 --cols A,B,C\n```\n\nOther `view` modes worth knowing: `annotated` (cell values + types/formulas + warnings), `stats` (numeric summaries), `issues` (broken formulas, empty sheets, missing refs).\n\n**Round-trip dump.** `officecli dump \"$FILE\" [path]` serializes the workbook — or one worksheet (`/Sheet1`, `/sheet[N]`) — into a replayable batch JSON; `officecli batch new.xlsx --input dump.json` replays it. Use it to learn from an existing workbook's structure or clone/adapt a template instead of reading raw OOXML. Coverage per `dump --help`; subtree dumps don't carry workbook-level resources (settings, named ranges) — the replay target must already define them.\n\n```bash\nofficecli dump \"$FILE\" -o blueprint.json            # whole workbook\nofficecli dump \"$FILE\" /Sheet1 -o sheet.json        # one worksheet\nofficecli batch new.xlsx --input blueprint.json\n```\n\n**Inspect one element.** Use XPath-style paths. Always quote — shells glob `[N]`.\n\n```bash\nofficecli get \"$FILE\" \"/Sheet1/A1\"            # one cell\nofficecli get \"$FILE\" \"/Sheet1/A1:D10\"        # range\nofficecli get \"$FILE\" \"/Sheet1/chart[1]\"      # chart\nofficecli get \"$FILE\" \"/Sheet1/table[1]\"      # ListObject\nofficecli get \"$FILE\" \"/namedrange[1]\"        # workbook-level named range\n```\n\nAdd `--depth N` to expand children; add `--json` for machine output. Full element list: `officecli help xlsx`.\n\n**Query across the workbook.** CSS-like selectors. Use for systematic checks (formula coverage, error cells, empty headers) rather than hand-walking.\n\n```bash\nofficecli query \"$FILE\" 'cell:has(formula)'       # every formula cell\nofficecli query \"$FILE\" 'cell:contains(\"#REF!\")'  # broken references\nofficecli query \"$FILE\" 'cell[type=Number]'       # typed filter\nofficecli query \"$FILE\" 'Sheet1!B[value!=0]'      # sheet-scoped\n```\n\nOperators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists).\n\n**Merge cells shortcut.** `officecli query $FILE merge` or `mergedrange` — both are aliases for `mergeCell`. Returns every merged range in the workbook without hand-walking `<mergeCell>` entries.\n\n**When the data is big enough that a row-walk is useless**, reach for Excel's own analytical elements:\n\n- Build a **pivot table** with `officecli add` (`--type pivottable`) to group/aggregate without writing 20 SUMIFs. Attach a **slicer** (`--type slicer`) to give the reader a filter UI.\n- Drop a **sparkline** (`--type sparkline`) in a row to show per-row trends — cheaper than one line chart per row and they print inline. `type` is a strict enum: **`line | column | stacked`** (plus aliases `winloss` / `win-loss` → `stacked`). Invalid `type=` values hard-fail — no silent fallback to `line` anymore.\n- Run `officecli help xlsx pivottable`, `officecli help xlsx slicer`, `officecli help xlsx sparkline` for the exact prop names.\n\n## Creating & Editing\n\nNinety percent of a build is cells, formulas, formatting, and one or two charts. The verbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`.\n\n### Cells and formulas\n\nSet a value and its format in one call. Never write `=` at the start of a formula — the CLI strips it.\n\n```bash\nofficecli set \"$FILE\" /Sheet1/B5 --prop formula=\"SUM(B2:B4)\" --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/C5 --prop formula=\"B5/A5\" --prop numFmt=\"0.0%\"\n```\n\nStructural properties (width, height, freeze, tabColor) live on row / col / sheet nodes:\n\n```bash\nofficecli set \"$FILE\" \"/Sheet1/col[A]\" --prop width=20\nofficecli set \"$FILE\" \"/Sheet1/row[1]\" --prop height=22\nofficecli set \"$FILE\" \"/Sheet1\" --prop freeze=A2 --prop tabColor=1F4E79\n```\n\n### Named ranges\n\nPrefer named ranges over `$B$6` in formulas. They self-document (`GrowthRate` beats `$B$6`) and they let you move the assumption cell without breaking formulas. Because `ref` values contain both `!` and `$`, add them through a batch heredoc:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"GrowthRate\",\"ref\":\"Sheet1!$B$6\"}}\n]\nEOF\n```\n\nSee `officecli help xlsx namedrange` for the full schema.\n\n**Batch JSON does NOT accept shell aliases.** Inside batch `props`, always use the full dotted name — `\"font.color\": \"FF0000\"`, `\"font.size\": 14`, never `\"color\": \"FF0000\"` (ambiguous: text vs fill). On a bare cell, even the shell form is rejected: `--prop color=1F4E79` errors with `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. Rule: in any batch JSON or cell prop, write `font.color` / `fill` explicitly. `parent` should be `\"/\"` for workbook-level elements and `\"/SheetName\"` for sheet-scoped; empty string is not equivalent.\n\n### Charts\n\nChart types live under `officecli help xlsx chart` — the enum is long (20+). Pick the right one for the message: column for category comparison, line for time series, pie only when slices are self-evidently proportional, scatter for correlation. Avoid exotic types unless they answer a specific question.\n\n**Three ways to feed chart data. Pick one per chart — mixing them at add-time is a common trap.**\n\n| Form | Shape | When to use |\n|---|---|---|\n| (a) inline `data` | `--prop data=\"Sales:100,200,300\" --prop categories=\"Jan,Feb,Mar\"` | Tiny demo charts, numbers you will not edit. Source of truth lives in the chart XML, not a cell. |\n| (b) 2D `dataRange` | `--prop dataRange=\"Sheet1!A1:B4\"` (first col = categories, first row = header / series name) | Normal case. Must be **2-D** — single column fails with \"Chart requires data\". |\n| (c) dotted per-series | `--prop series1.name=Sales --prop series1.values=\"Sheet1!B2:B4\" --prop series1.categories=\"Sheet1!A2:A4\"` | Multi-series charts where each series points at non-contiguous ranges, or you want explicit series naming. `series1.values` alone (no `categories`) emits a chart with `1,2,3` as the x-axis. |\n\n**The single-column trap.** `dataRange=\"Sheet1!B2:B13\"` looks like \"value column\" but the engine rejects it with `Chart requires data`. Either widen the range to include the category column (`A2:B13`), or switch to form (c) with explicit `series1.categories`.\n\n**Move / resize a chart after create:** `set chart[N] --prop anchor=\"F5:N25\"` (also `--prop x= --prop y= --prop width= --prop height=`). **Series are still immutable** — to add/change a series, `officecli remove` the chart and `officecli add` with the full series list. Note `remove chart[1]` shifts `chart[2] → chart[1]` and re-add **appends at the end** — to preserve chart order, remove all and rebuild in order.\n\n**Anchor sizing.** No auto-fit. A column chart with 5-6 categories + 2 series needs roughly `A5:L22` (12 cols × 18 rows) to show all labels uncut. Narrower and X-axis labels clip; wider and the chart can split across pages on print/export. If in doubt, start narrow, preview via `view html` (Read the returned HTML path), widen in increments. Page layout (below) is the other half of the fix.\n\n**Chart `dataRange` — always prefix with the sheet.** Even when the chart lives on the same sheet, write `dataRange=\"Summary!A17:C22\"`, not `A17:C22`. The sheet-less form works inconsistently; the prefixed form is 100% reliable.\n\nofficecli adds extended chart types the classic Excel object model lacks: `boxWhisker`, `waterfall`, `funnel`, `histogram`, `treemap`, `sunburst`, `pareto`. Use them when the data calls for them.\n\n**NEVER put unreplaced template tokens in chart title / series name / legend / axis title.** `$fy$24`, `{var}`, `<TODO>`, `$VAR`, `{{placeholder}}` render **literally** in the legend — validate passes, but a CFO sees `$fy$24` where \"FY2024\" should be. Always bind to final text or a cell reference (`title=\"FY2024 Revenue\"` or `series1.name=\"Sheet1!A1\"`).\n\n### Conditional formatting\n\nThree common flavors, each with its own prop shape (consult `officecli help xlsx cf`):\n\n- **Color scales**: cells shaded on a gradient by value — `type=colorscale` with `minColor` / `midColor` / `maxColor`.\n- **Data bars**: in-cell bars showing magnitude — `type=databar`. Set explicit `min` / `max` for consistent scaling across a column; defaults are valid if you omit them.\n- **Formula rules** (the `formulacf` element): highlight row when a condition is true — `type=formula` with `formula=\"$C2>1000\"` and a fill/font.\n\nRule: apply CF sparingly. A workbook where every cell is colored tells the reader nothing.\n\n### Data validation\n\nInput cells in trackers and templates MUST carry data validation. It's cheap and it stops entire classes of downstream bugs. **Three list-source patterns** — pick based on where the allowed values live.\n\n**(a) Inline list** — allowed values are short and fixed in the rule itself.\n\n```bash\nofficecli add \"$FILE\" /Sheet1 --type validation \\\n  --prop sqref=\"C2:C100\" --prop type=list \\\n  --prop formula1=\"Yes,No,Maybe\" \\\n  --prop showError=true --prop errorTitle=\"Invalid\" --prop error=\"Select from list\"\n```\n\n**(b) Named range (preferred for cross-sheet lookups)** — allowed values live in another sheet and may grow. Define the named range first, then reference it. Use a batch heredoc because `ref` contains `!` and `$`:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"StatusList\",\"ref\":\"Lookups!$A$2:$A$4\"}},\n  {\"command\":\"add\",\"parent\":\"/Sheet1\",\"type\":\"validation\",\"props\":{\"sqref\":\"B2:B100\",\"type\":\"list\",\"formula1\":\"=StatusList\"}}\n]\nEOF\n```\n\n**(c) Direct cross-sheet range** — no named range, raw `Lookups!$A$2:$A$4` inside `formula1`. Also needs a batch heredoc to keep `!` and `$` intact:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/Sheet1\",\"type\":\"validation\",\"props\":{\"sqref\":\"C2:C100\",\"type\":\"list\",\"formula1\":\"Lookups!$A$2:$A$4\"}}\n]\nEOF\n```\n\nIf you write the cross-sheet variant as `--prop formula1=...` on the shell, the `!` gets shell-mangled into `\\!` and the dropdown will silently fall back to no list. Verify with `officecli get \"$FILE\" /Sheet1/validation[N]` — `formula1=` must show a plain `!`, no backslash.\n\nOther common `type` values: `decimal`, `whole`, `date`, `textLength`, `custom`. See `officecli help xlsx validation` for operators and the full prop list.\n\n### Other elements (one-liners)\n\n- **Tables** (ListObjects) — `add --type table` with a range; gives auto-filter + structured refs. `officecli help xlsx table`.\n- **Comments** — `add --type comment`; use for documenting hardcoded assumptions. `officecli help xlsx comment`.\n- **Sheet reordering** — `officecli move`, not `swap`. `swap` only works on row/cell paths.\n- **New row/column with the neighbours' formatting** — `add \"$FILE\" /Sheet1 --from \"/Sheet1/row[5]\" --index 5` clones fills, fonts and formulas (refs shift); `add --type row` inserts a bare row.\n\n## Chart Axis-by-Role\n\nEditing a chart axis in place is cheaper than rebuilding the chart. Address axes by **role** (`value` = Y, `category` = X), not by index — the XML order isn't stable.\n\n```bash\nofficecli get \"$FILE\" \"/Sheet1/chart[1]/axis[@role=value]\"\nofficecli set \"$FILE\" \"/Sheet1/chart[1]/axis[@role=value]\" --prop min=0 --prop max=100000\nofficecli set \"$FILE\" \"/Sheet1/chart[1]/axis[@role=category]\" --prop title=\"Month\"\n```\n\nSafe props: `title`, `min`, `max`, `majorGridlines`, `visible`, `labelRotation`.\n\n## QA (Required)\n\n**Assume there are problems. Your job is to find them.**\n\nYour first workbook is almost never correct. Treat QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you were not looking hard enough. The formulas look fine **until** you check two of them against source cells.\n\n### Minimum cycle before \"done\"\n\n1. `officecli view \"$FILE\" issues` — empty sheets, broken formulas, missing refs.\n2. `officecli view \"$FILE\" annotated` (sample ranges) — values + types + warnings.\n3. For every Excel error type, query it:\n   ```bash\n   officecli query \"$FILE\" 'cell:contains(\"#REF!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#DIV/0!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#VALUE!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#NAME?\")'\n   officecli query \"$FILE\" 'cell:contains(\"#N/A\")'\n   ```\n4. `officecli validate \"$FILE\"` — safe with a resident open; `validate` flushes pending edits to disk itself.\n5. **Visual pass — walk every sheet via the HTML preview.** Run `officecli view \"$FILE\" html` and Read the returned HTML path. Each sheet renders with charts inline. Scan for `###`, truncated titles, placeholder tokens (`$fy$24`, `{var}`, `<TODO>`), sliced charts, white-slice pie charts, empty chart anchors — **STOP and fix before declaring done**. \"validate pass\" is not delivery; \"the preview looks like a real workbook\" is delivery. For human preview, run `officecli watch \"$FILE\"` (user opens the live preview at their own discretion) or have them open the `.xlsx` directly in Excel / WPS / Numbers.\n6. **Print layout fix (wide tables / multi-chart sheets).** When a sheet holds a chart or a wide table and the user will print it, set per-sheet page layout — but match the fit mode to the sheet's height:\n   ```bash\n   # Short summary / chart sheet → fit to one page.\n   officecli set \"$FILE\" \"/Summary\" --prop orientation=landscape --prop fitToPage=true\n   # Tall data table → fit width only (fitToPage=true would crush all rows onto one unreadable page).\n   officecli set \"$FILE\" \"/Data\" --prop orientation=landscape --prop fitToPage=1x0\n   ```\n   Outcome: charts/wide tables print without mid-chart splits; tall tables stay readable across natural page breaks. Apply to every sheet that holds a chart or a > 8-column table.\n7. If anything failed, fix, then **rerun the full cycle**. One fix commonly creates another problem.\n\n`officecli view issues` + `view html` are the structural QA pair: `issues` catches broken formulas and empty sheets; `view html` (Read the returned HTML path) catches `###`, truncation, and token leakage. Chart fill colors / theme tints can vary across viewers — spot-check in the user's target viewer when color fidelity matters.\n\n### Formula verification checklist\n\n- [ ] Pick 2-3 formulas at random. Run `officecli get` on each. Confirm the formula string is what you intended **and** `cachedValue=` is what you expect — arithmetic in your head.\n- [ ] **Cached value sanity on every summary cell.** Any cell that aggregates (COUNTA / COUNTIF / SUMPRODUCT / INDEX&MATCH) must have a plausible `cachedValue`. If a progress tracker shows `199 / 199 / 100%` on a blank template, the cache is lying — re-touch the formula via `set` (forces recompute) or manually set a correct cached value. Do NOT ship \"validate passes but the numbers are fiction\".\n- [ ] **Spot-check one cell per numeric column.** `%` columns showing integer `0.0%` throughout means the denominator is wrong or the numerator is cached stale — investigate one cell, fix the pattern.\n- [ ] Ranges include every row: off-by-one on `SUM(B2:B12)` when data goes to `B13` is the most common bug.\n- [ ] Cross-sheet formulas (`Sheet1!A1`) contain no `\\!`. If `officecli get` shows `Sheet1\\!A1`, the `!` was shell-corrupted — delete and re-enter via batch/heredoc.\n- [ ] Named ranges (`officecli get \"$FILE\" \"/namedrange[1]\"`) point at what their names claim.\n- [ ] Every `/` denominator is guarded — `IFERROR(x/y, 0)` or `IF(y=0, 0, x/y)`.\n- [ ] Chart data vs source cells: for every chart with inline data, spot-check data points against `officecli get` of the source cells.\n- [ ] Chart title / series name / legend contain **no** unreplaced tokens (`$...$`, `{var}`, `<TODO>`). Grep the chart via `officecli get /Sheet1/chart[N]`.\n\n### Template QA\n\nWhen editing a template, check for leftover placeholders — they look like content and slip past `validate`:\n\n```bash\nofficecli query \"$FILE\" 'cell:contains(\"{{\")'\nofficecli query \"$FILE\" 'cell:contains(\"xxxx\")'\nofficecli query \"$FILE\" 'cell:contains(\"TBD\")'\n```\n\n### Fresh eyes\n\nWhen you finish a workbook, open it fresh. Read `view text` / HTML preview top-to-bottom as if you are a new reviewer — look for formulas, numbers that look off, formatting inconsistency, missing data.\n\n### Honest limit\n\n`validate` catches schema errors, not design errors. A workbook can pass `validate` with every number wrong. The checklist above — especially spot-checking formulas against source cells — is how you catch what validation can't.\n\n## Known Issues & Pitfalls\n\n### The cross-sheet `!` trap (short)\n\nShells (bash history expansion, zsh splitting) and CLI arg parsing mangle `!` in `Sheet1!A1` into `\\!`. A formula containing `\\!` is silently broken — it renders as literal text and references nothing.\n\n**Fix.** Use a batch heredoc with single-quoted delimiter (`<<'EOF'`), which disables all shell expansion:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[{\"command\":\"set\",\"path\":\"/Summary/B2\",\"props\":{\"formula\":\"Revenue!B13\"}}]\nEOF\n```\n\n**Verify.** After writing, `officecli get` the cell; `formula=` must show a plain `!` with no backslash.\n\n### CLI bug backlog (short)\n\nCLI constraints and gaps to work around — not defects in the output file.\n\n- **Chart series are immutable after create** — to add/change a series: `remove` + `add` with the full series list. (Position is mutable: `set chart[N] --prop anchor=` / `x/y/width/height`.) `remove chart[N]` shifts subsequent indices down; re-add appends at end.\n- **Cross-sheet formula batches run fine through a resident** — a prior \"deadlocks even at 3-5 ops\" caution no longer reproduces. Pure value-set batches stay reliable at 50-80+ ops too. If you ever hit a hang, fall back to a non-resident one-big-batch or individual `set`. **Multiple resident processes on the same file/machine can still contend** — expect non-deterministic hangs if another agent/session holds a resident on the same file.\n- **Conditional formatting naming asymmetry** — the element name for `--type` is `conditionalformatting`; the path suffix is `/cf[N]`. Use `officecli help xlsx conditionalformatting` for schema, `/cf[N]` for paths.\n- **Sheet `position` prop on add** — help says Add processes `position`, but the prop is often ignored. Reorder with `officecli move --index` / `--after` / `--before` after creating the sheet.\n- **`remove /sheet[N]` cascade guard** — rejects sheet remove/rename when the sheet is referenced by validation / conditional format / sparkline / hyperlink / named range on another sheet. Remove those dependent elements first, then remove the sheet.\n- **Batch JSON rejects cell `color` alias** — inside batch `props`, `\"color\": \"FF0000\"` errors `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. The CLI at shell level accepts `--prop color=...` / `--prop size=14` as aliases on non-cell elements, but inside batch JSON on a cell always write the full dotted name: `\"font.color\"`, `\"font.size\"`, `\"font.name\"`.\n\n### Renderer caveats (cross-viewer color fidelity)\n\n`officecli view html` is the right tool for structural QA (overflow, truncation, placeholder leakage, layout) — Read the returned HTML path. Some chart rendering details vary across the viewer the end user opens the file in. Observed divergences:\n\n- **Pie / doughnut fill colors may collapse to a single theme tint** in some viewers (slices look \"all white\" or \"all one color\"). The file may be fine in the user's target viewer.\n- **Line chart / column chart series colors may drift** from the workbook theme in some viewers.\n- **Form-control checkboxes may render as double-boxed** in some viewers.\n\nBefore calling a color or chart \"broken\", open the file in the user's actual target viewer. If it looks correct there, the problem is viewer rendering, not data — do not chase it. The CLI's structural checks (`###`, truncation, placeholder text, layout) remain authoritative.\n\n### Escape layers (shell quoting is above; these are the extras)\n\n`$` is the shell layer (single-quote it, above). `\\n` / `\\t` in a prop value ARE interpreted by the CLI into a real newline / tab. Two more layers:\n\n- **JSON level (batch).** Standard JSON escapes — `\"\\n\"`, `\"\\t\"`, `\"\\\"\"`. A real backslash in the final string is `\"\\\\\\\\\"`.\n- **Excel level.** `\\n` in a cell is a real line break — pair with `--prop wrapText=true` so Excel shows the wrap. Works in a shell-quoted prop directly (`--prop value='a\\nb'`); `\"\\n\"` inside batch JSON gives the same. When in doubt, `officecli get` the cell and compare character-for-character.\n\n### Other common pitfalls\n\n| Pitfall | Fix |\n|---|---|\n| `--name \"foo\"` | All attrs go through `--prop`: `--prop name=\"foo\"` |\n| Guessing a prop name | `officecli help xlsx <element>` — don't improvise |\n| `--prop color=...` on a cell | Ambiguous — use `font.color` (text) or `fill` (bg). Also applies inside batch JSON: always use full dotted names, never shell aliases |\n| `#FF0000` hex colors | Drop the `#`: `FF0000` |\n| `--index` vs `[N]` | `--index` is 0-based (array); `[N]` paths are 1-based (XPath) |\n| Unquoted `[N]` in zsh/bash | Quote every path: `\"/Sheet1/row[1]\"` |\n| Sheet name with spaces | Quote full path: `\"/My Sheet/A1\"` |\n| Year showing as `2,026` | `--prop type=string` or `numFmt=\"@\"` |\n| Modifying a file open in Excel | Close it in Excel first |\n| `swap` not reordering sheets | `swap` is for rows/cells. Use `move --after` / `--before` / `--index` for sheets |\n| Cached values missing after write | New formulas get cached values when a human opens the file; `validate` accepts them either way |\n","skills/officecli-financial-model/SKILL.md":"---\nname: officecli-financial-model\ndescription: \"Use this skill when the user wants to build a financial model — 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising projections — in Excel. Trigger on: 'financial model', '3-statement model', 'P&L + BS + CF', 'DCF', 'WACC', 'NPV', 'terminal value', 'LBO', 'debt schedule', 'cash sweep', 'MOIC', 'IRR / XIRR', 'sensitivity table', 'scenario analysis', 'ARR model', 'unit economics', 'CAC / LTV', 'cap table forecast'. Output is a single formula-driven .xlsx. This skill is a scene layer on top of officecli-xlsx — it inherits every xlsx v2 rule (4-color code, visual floor, number formats, cache-drift, Known Issues, Delivery Gate minimum cycle). DO NOT invoke for a simple budget tracker, CSV dump, or operational KPI sheet — route those to officecli-xlsx base.\"\n---\n\n# OfficeCLI Financial-Model Skill\n\n**This skill is a scene layer on top of `officecli-xlsx`.** Every xlsx hard rule — shell quoting, incremental execution, Help-First Rule, visual delivery floor, CFO 4-color code (blue input / black formula / green cross-sheet / yellow-fill assumption), number-format standards (years as text, zero as `-`, `%` one decimal, negatives in parens), assumption-cell discipline, CSV batch import, chart data-feed forms (a/b/c), the 5-gate Delivery cycle, cache-drift guidance, Known Issues (the cross-sheet `!` trap, batch + resident for formulas, renderer caveats) — is **inherited, not re-taught**. This file adds only what a **financial model** requires on top: three-zone architecture, 3 model-type recipes (3-statement / DCF / LBO), sensitivity + scenario protocols, financial-function patterns, circular-reference discipline, and model-specific Delivery Gates 4–6.\n\nWhen the xlsx base rules cover it, the text here says `→ see xlsx v2 §X`. Read `skills/officecli-xlsx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Help-First Rule\n\nThis skill teaches what a financial model requires, not every CLI flag. When a prop name / alias / enum is uncertain, consult help BEFORE guessing: `officecli help xlsx [element] [--json]`. Help is authoritative for the installed version — when this skill and help disagree, **help wins**. Every `--prop X=` below was verified against `officecli help xlsx <element>`.\n\n## Mental Model & Inheritance\n\n**Inherits xlsx v2.** Read `skills/officecli-xlsx/SKILL.md` first. This skill assumes you know `create` / `open` / `close`, `set` values/formulas, `batch` heredocs for cross-sheet formulas, `/SheetName/A1` paths, named ranges, the 5-gate Delivery cycle, the cross-sheet `!` trap, and that **cross-sheet formulas go non-resident (single batch OR individual `set`), never batch-while-resident**.\n\n## Shell & Execution Discipline\n\nShell quoting, incremental execution, `$FILE` convention → see xlsx v2 §Shell & Execution Discipline. Same rules: quote every `[N]` path, single-quote any prop containing `$` (every number format here — `$#,##0;($#,##0);\"-\"` — needs single quotes), no hand-written `\\$`/`\\t`/`\\n`, one command at a time. Examples below use `$FILE` (`FILE=\"model.xlsx\"`).\n\n## Core Principles (identity)\n\nA financial model is an xlsx with a **decision-grade, formula-driven layer**: every output traces an unbroken chain to blue-font assumptions, every statement balances every period, every valuation is re-auditable. Eight deltas on top of a general xlsx:\n\n1. **Three-zone architecture mandatory:** Inputs → Calc → Outputs. Collapsing zones → unauditable.\n2. **Assumptions live in cells, never inside formulas.** `=B5*(1+Assumptions!GrowthRate)`, never `=B5*1.05`.\n3. **Statements balance every period.** `Assets − Liab − Equity = 0`, `CF.EndingCash = BS.Cash`. Gate 4 fails on `IMBALANCED`.\n4. **Hardcodes audited.** Calc sheets carry zero hardcoded numbers; Gate 6 counts.\n5. **Sensitivity / scenario is first-class.** 2-axis grid, dropdown `INDEX/MATCH` switch, or Base/Upside/Downside cols. Excel Data Tables not reliably supported — manual grids only.\n6. **Cached values on valuation cells load-bearing.** A valuation cell that ships with no cached result (or the `#OCLI_NOTEVAL!` sentinel) sends a blank/wrong number to non-recalculating readers. The evaluator now computes NPV / XNPV / IRR / XIRR; verify the cached value with a readback. Gate 5 spot-checks.\n7. **Circularity is a design choice.** Legitimate rings (interest ↔ cash, revolver plug ↔ ending cash) use `calc.iterate=true`. Accidental circularity is broken algebra — never papered with `iterate`.\n8. **Named ranges for ≥ 3-use assumptions.** `WACC`, `TaxRate`, `TerminalGrowth`, `ExitMultiple`, `ChurnRate`. Declared-unused names are dead decoration — Gate 6 flags.\n\n### Reverse handoff — when to go BACK to xlsx base\n\nStay in **xlsx base** for: budget trackers, CSV-to-report dumps, operational KPI sheets, simple templates, cap tables without forecast logic. Use **this skill** only when the ask mentions: 3-statement / DCF / WACC / NPV / TV / LBO / debt schedule / MOIC / IRR / unit economics / ARR roll-forward / sensitivity grid / scenario switch / pro forma.\n\n## Three-zone architecture (hard rule)\n\nEvery model in this skill builds on three zones. **Name them, tab-color them, and enforce them with executable audits.** Breaking the zone rule is the single most common cause of an unauditable model.\n\n| Zone | Sheet names (convention) | Tab color | Content | Hardcodes | Formulas |\n|---|---|---|---|---|---|\n| **Inputs** | `Assumptions`, `Inputs`, `Drivers` | Yellow `FFC000` | Raw drivers: growth rates, margins, tax, WACC, FTE, pricing, working-capital days | Blue `0000FF` on every cell | Allowed only for derived assumptions (e.g. `=MonthlyARPU*12`) |\n| **Calc** | `P&L`, `Balance Sheet`, `Cash Flow`, `DCF`, `Debt`, `ARR` | Blue `4472C4` | All derivations and statements | **Zero** (enforced by Gate 6) | Black `000000` for same-sheet, green `008000` for cross-sheet |\n| **Outputs** | `Summary`, `Dashboard`, `Sensitivity`, `Returns` | Green `70AD47` | KPIs, sensitivity grids, charts, returns waterfall | Only for labels (non-numeric); Gate 6 counts numeric hardcodes → 0 | Black / green per above |\n\n**Build order is cross-zone-aware.** Assumptions first, then Calc bottom-up on the dependency chain (`IS → BS → CF` for 3-statement; `FCF → WACC → NPV` for DCF), then Outputs last. Building Outputs first caches `0` everywhere and downstream inherits zeros.\n\n**Executable zone audit** (run before Gate 4):\n\n```bash\n# Calc zone: zero numeric hardcodes allowed. `cell:not(:has(formula))` selects the literal (non-formula) cells; `cell:has(formula)` selects the formula cells.\nHARDCODE=$(officecli query \"$FILE\" 'cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null) | select(.path | test(\"/(P&L|Balance Sheet|Cash Flow|DCF|Debt|ARR)/\"))] | length')\n[ \"$HARDCODE\" -eq 0 ] && echo \"Zone audit OK\" || { echo \"REJECT: $HARDCODE hardcoded numeric cells on Calc sheets — move to Assumptions\"; exit 1; }\n# Assumptions zone: should be non-zero.\nINPUTS=$(officecli query \"$FILE\" '/Assumptions/cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null)] | length')\n[ \"$INPUTS\" -ge 5 ] && echo \"Assumptions has $INPUTS hardcoded drivers\" || echo \"WARN: Assumptions has only $INPUTS inputs\"\n```\n\n## Print delivery (board / IC / LP)\n\nWhen the ask contains \"print\" / \"一页\" / \"董事会\" / \"投资人\" / \"IC memo\" / \"LP update\", the print pipeline must emit **only** the Outputs zone. Two artefacts:\n\n```bash\n# 1. Print_Area scoped to the Outputs sheet (Summary or Dashboard).\nofficecli add \"$FILE\" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Summary --prop 'refersTo=Summary!$A$1:$H$40'\n# 2. Hide every non-Outputs sheet — Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.\nfor S in Assumptions 'P&L' 'Balance Sheet' 'Cash Flow' DCF WACC Debt FCF 'S&U' Exit Returns; do\n  officecli raw-set \"$FILE\" /workbook --xpath \"//x:sheet[@name='$S']\" --action setattr --xml \"state=hidden\" || true\ndone\n# 3. fit-to-page landscape on Outputs sheet.\nofficecli raw-set \"$FILE\" /Summary --xpath \"//x:worksheet\" --action prepend --xml '<sheetPr xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><pageSetUpPr fitToPage=\"1\"/></sheetPr>'\n```\n\nDelete any `Print_Area` set on Calc sheets — conflicting scopes emit multi-page output with Assumptions / statement sheets leaking.\n\n## Build-order & cache-drift rule (critical for 3-statement)\n\nThree facts cause silent wrong numbers: (1) new formulas ship without cached values — Excel recomputes on open, HTML preview / older viewers do not; (2) downstream written in the same sequence as upstream caches `0` from upstream's pre-cache state; (3) cross-sheet `batch` while resident is open deadlocks at 3–5 ops.\n\n**Discipline (every recipe):**\n- Build order follows the data chain: `P&L → BS → CF` (3-statement); `FCF → WACC → NPV → Sensitivity` (DCF); `S&U → Debt → P&L → CF → Returns` (LBO).\n- After the cross-sheet chain, **cache-refresh pass:** re-issue `set` on every summary / valuation / balance-check cell, non-resident.\n- Spot-check: `officecli get \"$FILE\" /Summary/B2 --json | jq '.data.results[0].format.cachedValue'` returns a plausible non-null value. `null` means Excel will compute on open (OK for delivery). If a cell shows the `#OCLI_NOTEVAL!` sentinel: close residents, re-set; still unevaluated → cache-fallback (§Financial function patterns).\n\n## Recipes — three model types\n\nEach recipe below is **runnable skeleton, not finance theory**. Substitute numbers; don't restructure. All recipes assume `FILE=\"model.xlsx\"` is set and you have run `officecli create \"$FILE\"` + `officecli open \"$FILE\"`. Close with `officecli close \"$FILE\"` at the end.\n\n### Recipe A — 3-statement model (P&L + BS + CF)\n\n**What this recipe produces.** 4 sheets: `Assumptions`, `P&L`, `Balance Sheet`, `Cash Flow`, plus `Summary`. Year columns 2024A · 2025E · 2026E · 2027E. Balance-check row on BS; cash-reconciliation row on CF. Every statement row = formula → Assumptions.\n\n**Build order (MANDATORY).** `Assumptions → P&L → Balance Sheet → Cash Flow → Summary`. Do NOT build BS before P&L — `RetainedEarnings` depends on `NI`. Do NOT build CF before BS — `CF.OpeningCash = prior period CF.EndingCash` self-chain requires BS cash anchored for Y1. The skill's Gate 4 balance check fails silently if order is wrong.\n\n**Step 1 — sheets + tab colors + freeze panes.**\n\n```bash\nofficecli add \"$FILE\" / --type sheet --prop name=Assumptions --prop tabColor=FFC000\nofficecli add \"$FILE\" / --type sheet --prop name=P&L --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name='Balance Sheet' --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name='Cash Flow' --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name=Summary --prop tabColor=70AD47\nofficecli set \"$FILE\" /Assumptions --prop freeze=B2\nofficecli set \"$FILE\" /P&L --prop freeze=B3\nofficecli set \"$FILE\" \"/Balance Sheet\" --prop freeze=B3\nofficecli set \"$FILE\" \"/Cash Flow\" --prop freeze=B3\n```\n\n**Step 2 — assumptions (blue, yellow-fill on key drivers).** Year headers row 2, labels down col A, blue numeric inputs on B:E. Drivers: `RevenueGrowth`, `GrossMargin`, `OpExRatio`, `TaxRate`, `DaysReceivable/Inventory/Payable`, `CapExRatio`, `DepreciationYears`. `font.color=0000FF` on B:E. Yellow-fill (`fill=FFFF00`) the 3–5 scenario-switched drivers.\n\n**Declare named ranges for ≥3-use drivers and reference them** (`StartingARR`, `TaxRate`, `OpeningCash`, `GrowthRate`, `GrossMargin`). Formulas: `=StartingARR` not `=Assumptions!B4`; `=EBT*TaxRate` not `=EBT*Assumptions!B8`. Declared-unused names = dead decoration, Gate 6 rejects.\n\n**Step 3 — P&L rows (all formulas).** Rows: `Revenue` / `COGS` / `Gross Profit` / `OpEx` / `EBITDA` / `D&A` / `EBIT` / `Interest` / `EBT` / `Tax` / `Net Income`. Every row = formula referencing `Assumptions` or prior-row cells. Example revenue-side block — **substitute your row numbers**. Row-map for this example: `B3=Revenue, B4=COGS, B5=Gross Profit, B7=OpEx, B9=EBITDA, B10=EBIT, B15=Net Income`. Submit as single non-resident batch:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/P&L/B3\",\"props\":{\"formula\":\"Assumptions!B5\",\"font.color\":\"008000\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/C3\",\"props\":{\"formula\":\"B3*(1+Assumptions!C6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/D3\",\"props\":{\"formula\":\"C3*(1+Assumptions!D6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/E3\",\"props\":{\"formula\":\"D3*(1+Assumptions!E6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/B4\",\"props\":{\"formula\":\"-B3*(1-Assumptions!B7)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/B5\",\"props\":{\"formula\":\"B3+B4\"}}\n]\nEOF\n```\n\nAssumptions refs (`B5`, `C6`, `B7`) are also placeholder rows — better: **define named ranges** for each driver (Step 2) so formulas read `=StartingRevenue*(1+RevenueGrowth_Y2)` regardless of row layout. Repeat for `OpEx` / `D&A` / `Interest` / `Tax` / `NI`. `font.color=008000` on every cross-sheet-reference cell; same-sheet cells default `000000`. `numFmt='$#,##0;($#,##0);\"-\"'` on all $ rows.\n\n**Step 4 — Balance Sheet rows (all formulas).** Assets = `Cash + AR + Inventory + Net PP&E`. Liab = `AP + Debt`. Equity = `OpeningEquity + RetainedEarnings`. Working-capital rows use Days assumptions: `AR = Revenue × DaysReceivable / 365`. `Net PP&E` rolls forward: `Beg + CapEx − Depreciation`. **`BS.Cash` is NOT an independent plug** — it MUST equal `'Cash Flow'!B<ending-cash-row>` (populated in Step 5).\n\n**Retained Earnings — live formula every period.** `BS.RE(t) = BS.RE(t-1) + 'P&L'!NI(t) − Dividends(t)`. Hardcoded RE rounds to whole dollar → BS shows ±$1 off every period (CFO reads \"model doesn't balance\"). For Y1 Historical RE (no prior NI), compute via BS identity as a **live formula**: `BS!RE_Y1 = TotalAssets − TotalLiabilities − PaidInCapital`. Blue-font + classic comment on the Y1 cell; Y2..Y5 stay NI-driven.\n\n**Step 5 — Cash Flow rows (all formulas).** Operating: `NI + D&A − ΔWorkingCapital`. Investing: `−CapEx`. Financing: `ΔDebt − Dividends`. Ending Cash = `Opening + Operating + Investing + Financing`. **Year 2+ Opening Cash = prior period Ending Cash** — self-chain on the same sheet: `C17 = B19`, `D17 = C19`, `E17 = D19`. The Y1 `OpeningCash` is an Assumptions input.\n\n**Step 6 — Balance check + cash reconciliation rows (hard delivery checks).** Row-map for this example: `Balance Sheet: B10=Total Assets, B15=Total Liab, B17=Total Equity, B18=Balance Check`; `Cash Flow: B5=BS.Cash (cross-sheet anchor), B19=CF.Ending Cash, B21=CF-BS Cash Recon`. Substitute your layout's rows — the logic is the check, not the cell addresses.\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Balance Sheet/B18\",\"props\":{\"formula\":\"IF(ABS(B10-B15-B17)<0.01,\\\"OK\\\",\\\"IMBALANCED: \\\"&ROUND(B10-B15-B17,0))\",\"bold\":\"true\",\"font.color\":\"000000\"}},\n  {\"command\":\"set\",\"path\":\"/Cash Flow/B21\",\"props\":{\"formula\":\"IF(ABS(B19-'Balance Sheet'!B5)<0.01,\\\"OK\\\",\\\"CF != BS CASH: \\\"&ROUND(B19-'Balance Sheet'!B5,0))\",\"bold\":\"true\"}}\n]\nEOF\n```\n\nReplicate across columns C/D/E. Apply red fill (`fill=FFC7CE`) conditionally via `type=containsText --prop text=IMBALANCED` or `text=\"CF !=\"`. Gate 4 queries these rows and refuses delivery on any `IMBALANCED`.\n\n**Step 7 — cache refresh + format pass.** Re-set every summary cell on `Summary`, every balance-check / recon cell, and every cross-sheet reference on BS / CF (non-resident, single batch per sheet). Apply column widths (`col[A]=28`, `col[B:E]=15`), `numberformat='$#,##0;($#,##0);\"-\"'` on all dollar rows, header fills (`fill=1F3864`, `font.color=FFFFFF`, `bold=true`) on section-header rows (REVENUE / COGS / ASSETS / LIABILITIES). Header fill must cover A:E, not just the label cell (→ xlsx v2 §visual floor).\n\n**Step 8 — Summary / Dashboard KPIs + charts.** Minimum 4 KPIs: `Revenue 27E`, `EBITDA Margin 27E`, `Ending Cash 27E`, `Net Income CAGR` — each a formula referencing a statement cell, green font.\n\n**Minimum 3 charts on any Dashboard delivered to a board / executive audience** — one chart is a draft, three is a deliverable. Pre-populate `Summary!A10:E13` with Gross Margin / EBITDA Margin / NI Margin ratio rows (formulas referencing `P&L`) before adding the margin chart.\n\n```bash\n# (1) Top-line trend (Revenue + EBITDA).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=column --prop dataRange='P&L!A2:E5' --prop title='Revenue & EBITDA' --prop width=14cm --prop height=8cm\n# (2) Margin trend (Gross / EBITDA / NI margin).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=line --prop dataRange='Summary!A10:E13' --prop title='Margin trend' --prop width=14cm --prop height=8cm\n# (3) Cash trajectory (Ending Cash ± Runway).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=area --prop dataRange='Cash Flow!A19:E19' --prop title='Ending cash' --prop width=14cm --prop height=8cm\n```\n\n**Verification (run all three):**\n\n```bash\n# Balance check every period must say OK (range get → cells under .data.results[0].children[])\nofficecli get \"$FILE\" \"/Balance Sheet/B18:E18\" --json | jq '.data.results[0].children[] | .format.cachedValue // .text'\n# Cash recon every period must say OK\nofficecli get \"$FILE\" \"/Cash Flow/B21:E21\" --json | jq '.data.results[0].children[] | .format.cachedValue // .text'\n# Summary KPIs are plausible numbers, not null\nofficecli get \"$FILE\" \"/Summary/B2:B5\" --json | jq '.data.results[0].children[].format.cachedValue'\n```\n\n### Recipe B — DCF valuation\n\n**What this recipe produces.** Sheets: `Assumptions`, `FCF` (10-year forecast), `WACC` (panel), `DCF` (NPV + TV + equity bridge), `Sensitivity` (2-axis grid). Output: `Implied Equity Value` + `Implied Per-Share`, with a `WACC × g` sensitivity.\n\n**Build order.** `Assumptions → FCF → WACC → DCF → Sensitivity`.\n\n**Step 1 — named ranges for key drivers.** DCF's readability depends on names. Every formula below uses `WACC`, `TaxRate`, `g` — not `$B$6`:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"WACC\",\"ref\":\"WACC!$B$12\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"TaxRate\",\"ref\":\"Assumptions!$B$8\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"TerminalGrowth\",\"ref\":\"Assumptions!$B$15\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"NetDebt\",\"ref\":\"Assumptions!$B$20\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"SharesOut\",\"ref\":\"Assumptions!$B$21\"}}\n]\nEOF\n```\n\n**Step 2 — FCF build (10 years).** Columns B:K = Y1..Y10. Rows: `Revenue` (from growth) / `EBIT` (revenue × margin) / `EBIT × (1 − TaxRate)` (NOPAT) / `+ D&A` / `− CapEx` / `− ΔNWC` / `= FCF`. Use Assumptions-driven ratios (`CapEx = Revenue × CapExRatio`). All cells formulas, black font, `numFmt='$#,##0;($#,##0);\"-\"'`.\n\n**Step 3 — WACC panel.** On `WACC` sheet, an 8-row panel: `Risk-free rate` / `Equity risk premium` / `Beta` / `Cost of equity` (=Rf + β×ERP) / `Pre-tax debt cost` / `After-tax debt cost` (=×(1−TaxRate)) / `Equity weight` / `Debt weight` / `WACC` (=We×Re + Wd×Rd_after_tax). Inputs blue; derived rows black.\n\n**Step 4 — Terminal value + NPV + equity bridge.** Row-map: `DCF: B/C 3=TV, 4=PV explicit FCF, 5=PV terminal, 6=EV, 7=Net Debt, 8=Equity Value, 9=Per-Share`; `FCF: row 2 = periods (1..10), row 11 = FCF, B:K = Y1..Y10`. Substitute your rows. Notes column cells use `{\"value\":\"text\"}`, never `{\"formula\":\"...\"}` — formula-style prose yields `#NAME?` on open (see callout after Recipe C Step 5). On `DCF` sheet:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/DCF/B3\",\"props\":{\"value\":\"Terminal value (Gordon growth)\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C3\",\"props\":{\"formula\":\"FCF!K11*(1+TerminalGrowth)/(WACC-TerminalGrowth)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B4\",\"props\":{\"value\":\"PV of explicit-period FCF (10 yr)\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C4\",\"props\":{\"formula\":\"SUMPRODUCT(FCF!B11:K11/(1+WACC)^FCF!B2:K2)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B5\",\"props\":{\"value\":\"PV of terminal value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C5\",\"props\":{\"formula\":\"C3/(1+WACC)^10\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B6\",\"props\":{\"value\":\"Enterprise value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C6\",\"props\":{\"formula\":\"C4+C5\",\"bold\":\"true\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B7\",\"props\":{\"value\":\"Less: Net debt\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C7\",\"props\":{\"formula\":\"-NetDebt\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B8\",\"props\":{\"value\":\"Equity value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C8\",\"props\":{\"formula\":\"C6+C7\",\"bold\":\"true\",\"font.color\":\"000000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B9\",\"props\":{\"value\":\"Implied per-share\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C9\",\"props\":{\"formula\":\"C8/SharesOut\",\"bold\":\"true\",\"numberformat\":\"$0.00\"}}\n]\nEOF\n```\n\n**`NPV` vs `SUMPRODUCT` — both cache correctly.** The evaluator computes `NPV(rate, cross_sheet_range)` and caches the right value (verify with a readback). `SUMPRODUCT(values/(1+rate)^periods)` is the algebraic equivalent (period row `FCF!B2:K2 = 1..10` is a one-time setup); it is shown here as an OPTIONAL audit-readability choice — the explicit discounting series is sometimes easier for a reviewer to trace, not a cache workaround. Either form is fine. For irregular dates, `XNPV(rate, values, dates)` likewise caches; the SUMPRODUCT equivalent is `SUMPRODUCT(values/(1+rate)^((dates-base_date)/365))`.\n\n**Step 5 — 2-axis sensitivity grid (WACC × g).** 5×5 grid. Rows = WACC values `7.5% ... 11.5%`, cols = `g` values `1.5% ... 3.5%`. Each cell = one self-contained formula re-running the DCF with the grid's WACC and g substituted. Template:\n\n```bash\n# Cell D14 (first data cell, grid anchor at C14 = WACC label, C15 = first WACC value)\n# Substitute $D$13 (this cell's g) and $C15 (this cell's WACC) into a replicated EV + equity formula.\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Sensitivity/D15\",\"props\":{\"formula\":\"(NPV($C15,FCF!$B$11:$K$11)+(FCF!$K$11*(1+D$14)/($C15-D$14))/(1+$C15)^10+(-NetDebt))/SharesOut\",\"numberformat\":\"$0.00\"}}\n]\nEOF\n```\n\nCopy the formula across D15:H19 (5×5 grid). Row 14 carries g values (blue input); column C carries WACC values (blue input). Row 13 and column B carry labels. Apply 3-color gradient CF for quick-read (green = upside, red = downside):\n\n```bash\nofficecli add \"$FILE\" /Sensitivity --type conditionalformatting \\\n  --prop type=colorScale --prop ref=D15:H19\n```\n\n**No Excel Data Tables.** Excel's native `/Data/Table` 2-variable table is not reliably supported via the CLI — each grid cell MUST be an explicit formula. Copy the template, do not try `Data Table` input cells.\n\n**Verification.**\n\n```bash\nofficecli get \"$FILE\" \"/DCF/C8\" --json | jq '.data.results[0].format.cachedValue'   # equity value, plausible $\nofficecli get \"$FILE\" \"/DCF/C9\" --json | jq '.data.results[0].format.cachedValue'   # per-share, in $XX.XX range\nofficecli get \"$FILE\" \"/Sensitivity/F17\" --json | jq '.data.results[0].format.cachedValue'   # grid center cell, plausible\n```\n\nIf `C8` or `C9` come back `null` or show the `#OCLI_NOTEVAL!` sentinel, re-set them (non-resident) — see §Build-order & cache-drift.\n\n### Recipe C — LBO model\n\n**What this recipe produces.** Sheets: `Assumptions`, `S&U` (Sources & Uses), `Debt` (multi-tranche schedule), `P&L` (5-yr), `CF`, `Exit` / `Returns`. Outputs: `MOIC`, `IRR`, and a 4-tier returns waterfall. LBO is the stress test — expect circular refs (interest ↔ cash), deepest cross-sheet chains, and the heaviest use of named ranges.\n\n**Build order.** `Assumptions → S&U → P&L → Debt → CF → Exit → Returns`. P&L before Debt (debt interest depends on P&L EBIT for coverage checks); Debt before CF (CF uses interest + principal amortization). Enable `calc.iterate` before Step 5.\n\n**Step 1 — Sources & Uses (balance required, every fee line itemized).**\n\n```\nUses    = Purchase_EV (EntryEBITDA × EntryMultiple) + Transaction_fees (Purchase_EV × TxnFeePct, typ 1.5–2.5%)\n        + Financing_fees ((Senior + Mezz) × FinFeePct, typ 1–3%) + Refinanced_debt\nSources = Senior_TLB + Mezz + Revolver_drawn + Sponsor_equity\n```\n\n**Sponsor equity — pick one, never both.** (a) **Stated:** `Sponsor_equity = Assumptions!SponsorEquity`, then scale senior/mezz so Sources = Uses (fees absorbed by debt, not a silent plug). (b) **Solved:** `Sponsor_equity = Uses − Senior − Mezz − Revolver − Refinanced`, label \"Sponsor Equity (solved)\", no standalone Assumptions ref. Hardcoded `SponsorEquity` PLUS a `=Uses − Senior − Mezz` plug guarantees silent fee absorption — stated $140M vs plug $194.67M = $54.67M unaccounted fees, CFO rejection on sight.\n\n```bash\n# Sources = Uses hard check.\nofficecli set \"$FILE\" /S&U/B12 --prop formula='IF(ABS(SUM(B4:B7)-SUM(B9:B11))<1,\"BALANCED\",\"S&U IMBALANCE: \"&ROUND(SUM(B4:B7)-SUM(B9:B11),0))' --prop bold=true\n\n# Stated-vs-plug consistency (Gate 4 addendum; only run if you chose pattern (a)).\nSTATED=$(officecli get \"$FILE\" /Assumptions/B12 --json | jq -r '.data.results[0].format.cachedValue // \"null\"')\nPLUGGED=$(officecli get \"$FILE\" /S&U/B10 --json | jq -r '.data.results[0].format.cachedValue // \"null\"')   # B10 = sponsor-equity row on S&U\nDELTA=$(python3 -c \"print(abs(float('$STATED') - float('$PLUGGED')))\" 2>/dev/null || echo 99999)\npython3 -c \"import sys; sys.exit(0 if float('$DELTA') <= 1 else 1)\" && echo \"S&U sponsor OK (stated=$STATED plug=$PLUGGED)\" || { echo \"REJECT Gate 4 S&U: stated $STATED ≠ plug $PLUGGED (Δ=$DELTA) — fees silently absorbed\"; exit 1; }\n```\n\nEvery non-sponsor line on `S&U` is a blue Assumptions input (target EBITDA, entry multiple, fee %s) or a derived formula. No hardcoded Uses / Sources numbers.\n\n**Step 2 — Debt schedule (multi-tranche).** One row per tranche per year. Columns: `BeginningBalance` / `Mandatory amortization` / `Cash sweep` / `EndingBalance` / `AverageBalance` / `InterestExpense`. Senior TLB: 1% mandatory amortization + all excess cash to sweep. Mezz: 0% amortization, interest-only cash-pay. Row-map for this example (senior TLB tranche, year 2 column C): `C4=Beginning Balance, C5=Mandatory Amort, C6=Ending Balance, C7=Cash Sweep, C8=Average Balance, C9=Interest Expense`. `CF!C20` = free cash available to sweep (year-2 ending cash pre-sweep on CF sheet). Substitute your tranche row block per layout.\n\n```bash\n# year 2 senior TLB\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Debt/C4\",\"props\":{\"formula\":\"B6\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C5\",\"props\":{\"formula\":\"-C4*Assumptions!$B$30\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C6\",\"props\":{\"formula\":\"C4+C5+C7\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C7\",\"props\":{\"formula\":\"-MIN(-CF!C20,C4+C5)\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C8\",\"props\":{\"formula\":\"(C4+C6)/2\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C9\",\"props\":{\"formula\":\"-C8*Assumptions!$B$31\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}}\n]\nEOF\n# Add the sweep-rule comment as a classic comment (comment is NOT a cell prop — separate --type comment).\nofficecli add \"$FILE\" /Debt --type comment --prop ref=C7 --prop text='cash sweep capped at available cash and remaining tranche balance'\n```\n\n**Revolver capacity cap.** If your deal uses a revolver tranche, the revolver balance each period is bounded by the commitment ceiling:\n```\nRevolver_Balance = MIN(Assumptions!RevolverCapacity, MAX(0, prior_revolver + draw − paydown))\n```\nWithout the `MIN(capacity, ...)` outer, a shortfall quarter silently over-draws the facility.\n\nAdjust row indices to your layout. Repeat for each tranche (senior / mezz / revolver) and each year.\n\n**Step 3 — P&L (5-year) + interest from Debt.** P&L interest row pulls from Debt: `Interest = 'Debt'!TotalInterestRowY<N>`. This creates the **circular reference**: Interest → NI → CF → Cash Sweep → Debt balance → Interest.\n\n**Write-order warning.** `calc.iterate=true` governs _recalculation_, not write-phase. Appending the closing leg of a cross-sheet ring to a file that already contains the ring deadlocks the engine at 100% CPU regardless of `iterate`. For complex rings (multi-tranche LBO, revolver + TLB + mezz), use §Write-order surgery below (de-ring → write downstream → re-ring). Enable `calc.iterate=true` BEFORE writing ring formulas:\n\n```bash\nofficecli set \"$FILE\" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001\n```\n\n`iterate` converges via successive approximation for naturally-dampening loops (higher interest → less cash → less sweep → higher balance, bounded by EBIT). `#REF!` or divergent values = pause; fix algebra, do not raise `iterateCount` to 1000.\n\n**Step 4 — CF + cash sweep.** Ending cash = Opening + CFO − CapEx − Mandatory amort − Cash sweep. Cash sweep = `MIN(freeCashAfterCapEx, seniorDebtBalance + seniorMandatoryAmort)`. The `MIN` cap prevents swept-below-zero.\n\n**Step 5 — Exit + Returns.** Row-map: `Exit: B3=Exit EV, B4=Less: remaining debt, B5=Exit equity to sponsor`; `Returns: B3=MOIC, B4=IRR`.\n\n```bash\n# Values/formulas — single non-resident batch.\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Exit/B3\",\"props\":{\"formula\":\"'P&L'!F8*Assumptions!$B$25\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Exit/B4\",\"props\":{\"formula\":\"-('Debt'!F6+'Debt'!F13)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Exit/B5\",\"props\":{\"formula\":\"B3+B4\",\"bold\":\"true\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Returns/B3\",\"props\":{\"formula\":\"'Exit'!B5/('S&U'!B9)\",\"numberformat\":\"0.00\\\"x\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Returns/B4\",\"props\":{\"formula\":\"IRR({-'S&U'!B9,0,0,0,0,'Exit'!B5})\",\"numberformat\":\"0.0%\"}}\n]\nEOF\n# Classic comments — one --type comment per anchor cell.\nofficecli add \"$FILE\" /Exit --type comment --prop ref=B3 --prop text='Exit EV = Y5 EBITDA × exit multiple'\nofficecli add \"$FILE\" /Returns --type comment --prop ref=B3 --prop text='MOIC = exit equity / sponsor equity'\nofficecli add \"$FILE\" /Returns --type comment --prop ref=B4 --prop text='IRR — 5-yr, entry + exit only; use XIRR for mid-year dividends'\n```\n\n**Callout — labels: `comment` element vs Notes column vs `formula` (three distinct mechanics).**\n- **Hover tooltip** → `officecli add ... --type comment --prop ref=<cell> --prop text='...'`. The **`comment` key is NOT a valid prop on `set cell`** (not in `officecli help xlsx cell`) — it silently drops when embedded inside a `set cell` props dict. Use the dedicated element.\n- **Visible text in an adjacent Notes column** → `{\"command\":\"set\",\"path\":\"/DCF/D3\",\"props\":{\"value\":\"TV = FCF × (1+g) / (WACC−g)\"}}` — **`value`, not `formula`**, plain quoted string.\n- **Formula-style prose written as a real formula** → NEVER. `{\"formula\":\"FCF10*(1+g)/(WACC-g)\"}` produces `#NAME?` in Excel (`FCF10`, `g`, `WACC` are unbound identifiers in that cell context).\n\nFor mid-year dividends or partial exits, use `XIRR({cashflows}, {dates})` instead of `IRR`.\n\n**Step 6 — Returns waterfall (optional, 4-tier LP/GP).** Tiers: (1) LP preferred return 8% ; (2) GP catch-up to 20% ; (3) 80/20 split above hurdle ; (4) 100% to LP on loss. Each tier is a `MAX(0, MIN(...))` clamp. See §Sensitivity & scenarios for the general grid pattern.\n\n**Verification.**\n\n```bash\nofficecli get \"$FILE\" /S&U/B12 --json | jq '.data.results[0].format.cachedValue // .data.results[0].text'   # must say BALANCED\nofficecli get \"$FILE\" /Returns/B3 --json | jq '.data.results[0].format.cachedValue'                # MOIC, expect 2.0x-4.0x typical\nofficecli get \"$FILE\" /Returns/B4 --json | jq '.data.results[0].format.cachedValue'                # IRR, expect 0.15-0.30 typical\n# Iterate converged?\nofficecli query \"$FILE\" 'cell:contains(\"#REF!\")' --json | jq '.data.results | length'   # must be 0\n```\n\n## Sensitivity & scenarios\n\n**Three patterns, pick one:**\n- **(a) Base / Upside / Downside columns** on Assumptions — side-by-side scenarios, dropdown-less switch via an \"Active\" column + `INDEX/MATCH`.\n- **(b) Dropdown + `INDEX/MATCH` switch** — one validation dropdown on Summary drives every driver via `INDEX(Base:Downside, MATCH(Dropdown, ScenLabels, 0))`.\n- **(c) 2-axis sensitivity grid** — 5×5 or 7×7, one self-contained formula per cell, row/col headers are the two drivers. See Recipe B Step 5 for WACC × g.\n\nMixing (a)+(b) creates circular input (scenario picked by dropdown AND overwritten by Active column) — pick one.\n\n**Grid rule:** each cell substitutes row-driver and col-driver into a self-contained copy of the output formula. Cannot reference the `WACC` named range (that's the panel) — reference the grid's axis cell.\n\n**Dropdown scenario switch.** One `validation` dropdown on Summary drives every `Assumptions` row:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/Summary\",\"type\":\"validation\",\"props\":{\"sqref\":\"B1\",\"type\":\"list\",\"formula1\":\"Base,Upside,Downside\"}},\n  {\"command\":\"set\",\"path\":\"/Assumptions/B5\",\"props\":{\"formula\":\"INDEX(C5:E5,MATCH(Summary!$B$1,$C$4:$E$4,0))\"}}\n]\nEOF\n# If you want a hover tooltip on B5, add it separately:\nofficecli add \"$FILE\" /Assumptions --type comment --prop ref=B5 --prop text='Revenue growth — picked by Summary!B1 scenario dropdown'\n```\n\nEvery `Assumptions` driver row gets the same `INDEX/MATCH`. Base / Upside / Downside columns on C:E stay blue (hardcoded scenario inputs).\n\n**Football-field chart pattern (DCF valuation summary).** Horizontal Low→High bars for 3–5 valuation methods (DCF base, DCF bear, Trading comps, Precedent txns, LBO floor) stacked vertically. On a `Football` sheet: col A = method label, col B = Low $, col C = High $, col D = `=C−B` (width). Chart as a stacked bar with column B as an invisible first series (white/no-fill) and column D as the visible series — `dataRange=Football!A3:D7`, `chartType=bar`. Excel reads this as a floating bar per method.\n\n## Financial function patterns\n\nTerse reference — not a finance textbook. If you don't know what these do, pause and ask the user.\n\n| Function | Prefer over | Why |\n|---|---|---|\n| `XNPV(rate, values, dates)` | `NPV` | Irregular cash flow dates (M&A close mid-year, staggered tranches) |\n| `XIRR(values, dates)` | `IRR` | Irregular dates; multiple sign changes handled better |\n| `INDEX(range, MATCH(lookup, key, 0))` | `VLOOKUP` | Insert-safe (VLOOKUP breaks when a column is inserted in the source range) |\n| `IFERROR(x/y, 0)` or `IF(y=0, 0, x/y)` | bare division | Guard every `/` in a financial model — `#DIV/0!` shipped = delivery failure |\n| `MIRR(values, financeRate, reinvestRate)` | `IRR` with sign flips | When cash-flow pattern has 2+ sign changes |\n| `SUMIFS(sumRange, criteriaRange1, criterion1, ...)` | `SUMPRODUCT((...))` array | Clearer intent for conditional sums; either evaluates correctly |\n\n**Evaluator coverage — verify, don't preemptively hardcode.** The CLI evaluator computes the finance functions this skill uses — `NPV` / `XNPV` / `IRR` / `XIRR`, array-literal formulas (`IRR({...})`), and `SUMPRODUCT(1/COUNTIF(range,range))` distinct-count — and caches the correct value with `evaluated:true`. There is no `NPV→0` rewrite obligation and no distinct-count `1/N` trap. The honest rule: build the formula you mean, then verify the cached value with a readback (`get ... --json | jq '.data.results[0].format.cachedValue'`). Only if a specific cell genuinely comes back with the `#OCLI_NOTEVAL!` sentinel (formula written before its inputs, or an unsupported function) should you fall back — first re-set non-resident after `close`; if it still won't evaluate, hardcode the computed value with a blue font and a classic comment via `officecli add \"$FILE\" /Sheet --type comment --prop ref=<cell> --prop text='cached valuation; refreshes on open in Excel — do not edit'`, and disclose in delivery notes.\n\n## Circular references & iterative calc\n\n**Enable `calc.iterate` ONLY when circularity is algebraically justified:** Interest ↔ Cash (LBO revolver / cash sweep), Tax shield ↔ NI (rare — most 3-statement models compute interest before tax and avoid), Revolver plug ↔ Ending cash (corporate cash waterfall with min-cash).\n\n```bash\nofficecli set \"$FILE\" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001\n```\n\n`iterateCount=100` / `iterateDelta=0.001` are Excel defaults, fine for naturally dampening loops.\n\n### Write-order surgery (de-ring → write downstream → re-ring)\n\n`calc.iterate` controls recalc, not write-phase. Appending the closing leg of an already-wired cross-sheet ring (Debt.Interest ↔ CF.Cash ↔ Debt.CashSweep) deadlocks at 100% CPU; `view html` / `get` also hang on a non-converged ring.\n\n**3-step playbook:**\n1. **De-ring** — write Debt with the 10–20 ring cells set to literal `0` (e.g. `C7=0`, not `=-MIN(...)`). Removes the ring.\n2. **Write downstream** — build all non-circular chains (P&L, CF, Exit, Returns, Summary, grid) non-resident, one heredoc per sheet. Everything caches against the zeroed cells.\n3. **Re-ring** — close all residents, re-set each circular cell with its real formula, one `set` per cell, non-resident.\n\n**Acceptance.** `get /Debt/C7 --json | jq '.data.results[0].format.cachedValue'` returns non-zero non-null. If a cell still deadlocks, leave `=0` + classic comment `\"circular; recalculates in Excel on F9\"`, flag at delivery. Never paper over with `iterateCount=1000`.\n\n**Do NOT use `iterate` as a band-aid for `#REF!` / divergent values.** Raising `iterateCount` to 1000 hides the bug and ships a plausibly-wrong value; `validate` does not catch it. Break the loop algebraically (e.g. interest on opening balance only, not average).\n\n**Verify convergence.** Read the loop cell, bump a driving assumption and back, re-read — values must match:\n\n```bash\nV1=$(officecli get \"$FILE\" /Debt/C9 --json | jq '.data.results[0].format.cachedValue')\nofficecli set \"$FILE\" /Assumptions/B31 --prop value=0.085\nofficecli set \"$FILE\" /Assumptions/B31 --prop value=0.0845\nV2=$(officecli get \"$FILE\" /Debt/C9 --json | jq '.data.results[0].format.cachedValue')\n[ \"$V1\" = \"$V2\" ] && echo \"Iterate converged\" || echo \"WARN: drift V1=$V1 V2=$V2 — tighten iterateDelta or check algebra\"\n```\n\n## Audit & Delivery Gate\n\n**Assume there are problems.** First build is almost never correct. Run every gate below; every check must print its success line. `validate` passing is not delivery — the model can pass schema and still be wrong by a factor of 10.\n\n### Gates 1–3 — inherited from xlsx v2 verbatim\n\n→ see xlsx v2 §QA minimum cycle (Gates 1–3 cover `view issues`, error-cell query, `validate` after close). Run them first, exactly as written in xlsx v2. No financial-model-specific tweaks.\n\n### Gate 4 — statement integrity (3-statement & LBO)\n\nBalance-check and cash-reconciliation rows produced by Recipe A / C must show `OK` / `BALANCED` every period. `query` the check rows and refuse on any `IMBALANCED` / `CF !=`:\n\n```bash\nBS_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"IMBALANCED\")' --json | jq '.data.results | length')\nCF_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"CF !=\")' --json | jq '.data.results | length')\nSU_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"S&U IMBALANCE\")' --json | jq '.data.results | length')\nif [ \"$BS_FAIL\" -eq 0 ] && [ \"$CF_FAIL\" -eq 0 ] && [ \"$SU_FAIL\" -eq 0 ]; then\n  echo \"Gate 4 OK (balance + recon + S&U all pass)\"\nelse\n  echo \"REJECT Gate 4: BS=$BS_FAIL CF=$CF_FAIL S&U=$SU_FAIL\"; exit 1\nfi\n```\n\nIf any fail, the model is silently wrong — fix the upstream chain before delivery. Most common cause: a cross-sheet formula stored `\\!` (shell-mangled) — run `officecli query \"$FILE\" 'cell:contains(\"\\\\\\\\!\")'` and re-enter via batch heredoc.\n\n### Gate 5 — cached-value sanity on valuation cells\n\nNPV / IRR / XIRR / equity-bridge / MOIC / summary KPI cells that ship unevaluated (`#OCLI_NOTEVAL!` sentinel, typically because the formula was written before its inputs) send a blank/wrong number to a reader who does not recalc on open. List every valuation cell and confirm a cached value is present:\n\n```bash\n# Customize the path list per recipe — this is the DCF example\nfor P in \"/DCF/C4\" \"/DCF/C5\" \"/DCF/C6\" \"/DCF/C8\" \"/DCF/C9\"; do\n  V=$(officecli get \"$FILE\" \"$P\" --json | jq -r '.data.results[0].format.cachedValue // \"null\"')\n  if [ \"$V\" = \"null\" ] || [ \"${V#\\#OCLI_NOTEVAL}\" != \"$V\" ]; then\n    echo \"REJECT Gate 5: $P unevaluated (cached=$V) — re-set after close (see §Build-order & cache-drift)\"; exit 1\n  fi\n  echo \"Gate 5 $P: cached=$V OK\"\ndone\n```\n\nFor LBO, extend the list: `/Exit/B5`, `/Returns/B3`, `/Returns/B4`. For 3-statement, extend with `/Summary/B2:B5`.\n\n### Gate 6 — hardcode / zone discipline\n\nEvery Calc sheet has zero numeric hardcodes. Executable:\n\n```bash\n# `cell:not(:has(formula))` selects the literal cells (and `cell:has(formula)` the formula cells).\nHARDCODE=$(officecli query \"$FILE\" 'cell[type=Number]' --json \\\n  | jq '[.data.results[] | select(.format.formula == null) | select(.path | test(\"/(P&L|Balance Sheet|Cash Flow|DCF|Debt|FCF|WACC|Exit|Returns)/\"))] | length')\n[ \"$HARDCODE\" -eq 0 ] && echo \"Gate 6 OK (no hardcodes on Calc sheets)\" || { echo \"REJECT Gate 6: $HARDCODE hardcoded numeric cells on Calc zone — move to Assumptions\"; exit 1; }\n\n# Named-range coverage + dead-decoration audit: ≥3 ranges declared AND each referenced by ≥1 formula.\nNR=$(officecli query \"$FILE\" namedrange --json | jq '.data.results | length')\n[ \"$NR\" -ge 3 ] && echo \"Gate 6 OK ($NR named ranges)\" || echo \"WARN Gate 6: only $NR named ranges\"\nDEAD=0\nfor NR_NAME in $(officecli query \"$FILE\" namedrange --json | jq -r '.data.results[].format.name'); do\n  # Match the formula SOURCE (`formula~=`), not the cached/displayed RESULT — `:contains` would scan\n  # the computed value and report every name as dead (false reject).\n  USES=$(officecli query \"$FILE\" \"cell[formula~=$NR_NAME]\" --json | jq '.data.results | length')\n  [ \"$USES\" -ge 1 ] && echo \"  $NR_NAME: $USES uses OK\" || { echo \"  WARN: $NR_NAME unused\"; DEAD=$((DEAD+1)); }\ndone\n[ \"$DEAD\" -eq 0 ] && echo \"Gate 6 named-range audit OK\" || { echo \"REJECT Gate 6: $DEAD dead-decoration name(s)\"; exit 1; }\n```\n\n### Gate 5b — visual audit via HTML preview (mandatory)\n\nGates 1–4/6 are grep defenses — they cannot see a rendered sheet. Run `officecli view \"$FILE\" html` and Read the returned HTML. Walk every sheet (inherits xlsx v2 visual floor):\n\n- No `###` in any numeric cell (widen column).\n- No truncated labels / section headers (widen column or `alignment.wrapText=true`).\n- No placeholder tokens (`TBD`, `{var}`, `xxxx`) — Gate 6.1 grep below.\n- Balance-check / recon rows say `OK` / `BALANCED` every period column.\n- Dashboard charts render, y-axis = 0 on ARR/revenue lines, source data matches statement sheet.\n- Sensitivity grid colors read green (upside) → red (downside) — color-scale CF applied.\n- No stale cached `0` on summary KPIs; if present, run cache-refresh pass.\n\nREJECT on any defect. **Human preview:** `officecli watch \"$FILE\"`, or open in Excel / WPS / Numbers — final colors + chart fidelity only fully render in the target viewer.\n\n### Gate 6.1 — token / placeholder sweep\n\n```bash\nLEAK=$(officecli view \"$FILE\" text | grep -niE 'TBD|\\(fill in\\)|xxxx|lorem|\\{\\{|placeholder|coming soon')\n[ -z \"$LEAK\" ] && echo \"Gate 6.1 OK (no placeholder tokens)\" || { echo \"REJECT Gate 6.1:\"; echo \"$LEAK\"; exit 1; }\n```\n\n### Honest limit\n\n`validate` catches schema errors, not finance errors. A model passes `validate` with `BS.Cash` hardcoded to force balance, an `NPV` cached at `0`, a sensitivity grid all-zero because it was built before FCF, a `#NAME?` runtime on a `P&L`-named sheet with unquoted refs. Gates 4 / 5 / 6 / 5b exist because schema-level `validate` cannot catch any of this.\n\n## Known Issues & Pitfalls\n\n→ Base pitfalls (cross-sheet `!` trap, batch JSON dotted-name rule, renderer caveats): see xlsx v2 §Known Issues & Pitfalls — all apply.\n\nFinancial-model-specific:\n\n- **AP sign on COGS.** Accounts Payable: if COGS is stored negative on the P&L, AP formula must negate — `=-COGS*DaysPayable/365`. Wrong sign inflates NWC and flips CF direction. Silent; passes `validate`.\n- **`#NAME?` not caught by `query` / `validate`.** A cross-sheet formula referencing `P&L!B3` without quoting the sheet name (because `&` is special) lands at runtime as `#NAME?`. Always write cross-sheet refs as `'P&L'!B3` — single-quote the sheet name if it contains `&`, space, `(`, `)`, etc. Gate 5b visual check is the only detection.\n- **Iterative calc silent non-convergence.** `calc.iterate=true iterateCount=100` converges at whatever the cap lands on — even if the true answer is 2× that. Always run convergence verify (§Circular references). Complex LBO rings (multi-tranche debt + sweep + tax shield) may not converge; when `cachedValue=0` on a ring cell, use §Write-order surgery.\n- **Batch-while-resident deadlock on circular writes.** Writing the closing leg of a cross-sheet ring via `batch` with a resident open deadlocks at 100% CPU. Even single `set` on a ring cell can hang. Fix: close residents, write the ring in two passes per §Write-order surgery. Non-resident single-heredoc is the only safe form.\n- **Cross-sheet cached value stale in `view html`.** Downstream written in the same sequence as upstream caches `0`. Excel resolves on open; HTML preview does NOT. Re-set every downstream non-resident after the chain (§Build-order & cache-drift).\n- **`NPV()` / `XNPV()` evaluate and cache correctly.** The evaluator computes both (same-sheet and cross-sheet); no rewrite is required. `SUMPRODUCT(values/(1+rate)^periods)` remains an optional audit-readability alternative, not a cache workaround.\n- **Sensitivity-grid build order still matters.** A grid cell written before its FCF/WACC inputs exist evaluates against empty inputs and may cache a wrong/blank value. Build FCF + WACC + DCF first, then the grid in a separate non-resident batch, and verify (`jq '.data.results[0].format.cachedValue'`). This is an ordering discipline, not an evaluator limitation.\n- **`BS.Cash` = CF ending cash always** (including Y1: `BS.Cash = 'Cash Flow'!B19`). Never an independent plug or Assumptions ref — a plugged `BS.Cash` hides balance errors.\n- **Year 2+ `Opening Cash` = prior period `Ending Cash`** (`C17=B19`, `D17=C19`). Independent Y2+ opening-cash inputs silently drift from BS.\n- **Waterfall chart \"total\" bars.** `chartType=waterfall` cannot mark total programmatically — use `colors=` convention (dark = total, medium = positive, red = negative). See `help xlsx chart`.\n- **DCF per-share when `SharesOut` is a formula.** `=BasicShares + OptionPool × ExerciseAssumption` → add a blue-font assumption cell and point the `SharesOut` named range at the computed cell, not the raw input.\n\n## Help pointer\n\nWhen in doubt: `officecli help xlsx [element] [--json]`. Help is the authoritative schema; this skill is the decision guide for financial-modeling deltas.\n","skills/officecli-pptx/SKILL.md":"---\nname: officecli-pptx\ndescription: \"Use this skill any time a .pptx file is involved -- as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file; editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions 'deck', 'slides', 'presentation', 'pitch', or references a .pptx filename.\"\n---\n\n# OfficeCLI PPTX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good slides look like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help pptx                         # List all pptx elements\nofficecli help pptx <element>               # Full element schema (e.g. shape, chart, animation, connector, zoom, group)\nofficecli help pptx <verb> <element>        # Verb-scoped (e.g. add shape, set slide)\nofficecli help pptx <element> --json        # Machine-readable schema\n```\n\nHelp reflects the installed CLI version. When skill and help disagree, **help is authoritative**. Triggers to run help immediately: `UNSUPPORTED props:` warning, unknown animation preset, `connector.shape=` enum drifts, prop-vs-alias (`lineWidth` vs `line.width`, `color` vs `font.color`).\n\n## Shell & Execution Discipline\n\n**Shell quoting (zsh / bash).** ALWAYS quote element paths (`\"/slide[1]/...\"`) — zsh globs unquoted `[1]` to `no matches found`. Escaping happens at three layers — keep them separate (the CLI handles the second for you):\n\n1. **Shell.** `$` in a value still belongs to the shell — single-quote the whole value: `--prop text='$15M'`. Double-quoted `\"$15M\"` gets expanded to `M`. The CLI does NOT unescape `\\$` for you.\n2. **CLI (`text=`).** The two-char escapes `\\n` and `\\t` ARE interpreted, consistently across pptx / docx / xlsx — `\\n` is a line / paragraph break, `\\t` is a tab. To produce a literal backslash-n in text, double it (`\\\\n`); this is rarely what you want.\n3. **JSON (batch heredoc).** The recipes below pipe `cat <<EOF | officecli batch` **unquoted** so `$SLIDE` / `$FILE` expand inside the body. That same unquoted heredoc also expands a literal `$` in a value: `\"$1.42\"` silently becomes `.42` and `\"$2.4B\"` becomes `.4B` (`$1` / `$2` are empty shell params). **Escape currency as `\\$`** — `\"text\":\"\\$1.42\"` — which still lets `$SLIDE` expand. `\\n` / `\\t` inside the JSON work either way. A fully-quoted `<<'EOF'` protects every `$` but then `$SLIDE` won't expand, so only use it when the body has no shell variables. After writing money values, `view text` and confirm the `$` survived.\n\nIf in doubt, `view text` after writing and compare character-for-character.\n\n**Incremental execution.** One command → check exit code → continue. A 50-command script that fails at command 3 cascades silently. After any structural op (new slide, chart, animation, connector) run `get` before stacking more.\n\n## Requirements for Outputs\n\nThese are the deliverable standards every deck MUST meet. Violating any one = not done, regardless of content quality.\n\n### All decks\n\n**One idea per slide.** If a slide needs a second title to explain what it covers, split it. Dense \"everything about X\" slides lose the audience inside 3 seconds. Use a section divider to group related one-idea slides, not a mega-slide.\n\n**Explicit type hierarchy — do NOT rely on theme defaults.** Theme defaults drift between masters. Set sizes explicitly on every text shape.\n\n| Element | Minimum | Typical | Min shape height |\n|---|---|---|---|\n| Slide title | **≥ 36pt** bold | 36–44pt | ≥ 2cm |\n| Section / subtitle | ≥ 20pt | 20–24pt | ≥ 1.2cm |\n| Body text | **≥ 18pt** | 18–22pt | ≥ 1cm |\n| Caption / axis label | ≥ 10pt muted | 10–12pt | ≥ 0.6cm |\n\nRule of thumb: **min shape height ≈ font_pt × 0.05cm**. An 18pt sublabel in a 0.8cm-tall box will overflow — `view annotated` catches this.\n\nTitle must be **≥ 2× body size** (36pt over 20pt works; 28pt over 20pt looks timid). Four legit exceptions to body ≥ 18pt: chart axis labels, legends, footer / page number, and ≤ 5-word KPI sublabels (e.g. \"Active users\"). Descriptive sentences must be ≥ 18pt. Left-align body; center only titles and hero numbers. If \"the cards won't fit\", drop cards instead of shrinking font.\n\n**Two fonts max, one palette.** One heading font + one body font (e.g. Georgia + Calibri) — a third *display* face is fine only for big numerals or the cover title, as long as that heading+body pair stays intact. One dominant brand color (60–70% weight) + one supporting + one accent. Never mix 4+ colors in body content. **The palettes and font pairings in Design Principles are a floor, not a menu:** if the user gave brand colors/fonts or an existing template, match those first; otherwise the named sets are calibrated seeds — blend or diverge freely, as long as the result isn't *worse* than them and still clears the contrast floor.\n\n**Every slide carries a non-text visual — one that informs.** Shape, chart, icon, gradient band that carries meaning, not decoration. A bullet-only deck is interchangeable with a Word doc. Exceptions: literal quote slides, code blocks, a single summary-table slide.\n\n**Less is more — every element earns its place.** The visual rule above guards against bullet-walls; it is not licence to clutter. Don't pad with decorative stats, icons, or filler sections that don't inform (\"data slop\"). If a slide feels empty, fix it with layout and whitespace, not invented content — cut scope rather than bulk it up, and flag a larger addition instead of making it unprompted.\n\n**Speaker notes on every content slide.** `--type notes --prop text=\"...\"`. The speaker needs a script; the audience shouldn't read the slide verbatim.\n\n**Copy reads human, not AI.** Titles orient on content, not punchline. No \"It's not X. It's Y.\", no manufactured tension, no faux-insight (\"The magic moment\"), no one-word drama (\"Momentum.\"). Cut hype adjectives (seamless, robust, game-changing) — let the number carry it.\n\n**Preserve existing templates.** When a file already has a theme and masters, match them. Existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY deck)\n\nBefore declaring done, the per-slide render (see QA) MUST satisfy:\n\n- **No placeholder tokens rendered as content.** `{{name}}`, `$fy$24`, `<TODO>`, `lorem`, `xxxx`, empty `()`/`[]` in chart titles never appear.\n- **No overflow off-edge, no clipped text in shapes.** `view issues` flags both (`shape_off_slide` + a text-fit hint). To fix a clip: grow the box or shorten the value — never trim content to fit.\n- **Cover carries its orienting elements.** Title + subtitle + presenter/client + date + a brand band or key-takeaway strap — a title-only cover reads as a stub. Generous whitespace around them is still right; rich ≠ crowded.\n- **Contrast.** `view issues` auto-flags the common case — opaque dark text on a shape's own dark fill (`low_contrast`). It can't see the rest: icon / chart-series fills, scheme/inherited colors, or text over a *separate* background shape. So on any fill with brightness < 30% (`1E2761`, `36454F`, deep forest / berry / cherry), still confirm every body run, card body, chart series, and icon is `FFFFFF` or brightness > 80% — mid-gray (`6B7B8D` ≈ 44%) reads on a laptop and vanishes on projection. Spot-check via `view html` after the dark-fill pass.\n\nIf any fails, STOP and fix before declaring done.\n\n## Design Principles\n\nA deck is not a document. The audience has 3 seconds to get each slide. Before adding anything, ask: \"If the audience reads only the biggest element and glances once, do they get the point?\" If they have to read the bullets, the biggest element is wrong.\n\n### Grid, margins, negative space\n\nStandard widescreen is **33.87 × 19.05cm**. Treat it as a 12-column grid internally:\n\n- **Edge margin ≥ 1.27cm** (0.5\") on all sides.\n- **Inter-block gap ≥ 0.76cm** (0.3\") between cards / columns / rows — pick one value (0.76 or 1.27cm) and use it everywhere; mixed gaps look unfinished.\n- **≥ 20% negative space per slide.** Filling every pixel reads as amateur.\n- **Compose, don't web-center.** Whitespace is structural: a slide top-weighted with open space in the lower third is correct composition, not an empty defect. Intentional asymmetry (content left, breathing room right) reads more designed than centering everything — don't fill a gap just because it's there.\n- For card grids: `usable = 33.87 − 2·margin − (N−1)·gap`, then `col_width = usable / N`. Don't hand-pick x coordinates.\n\n### Font pairings\n\nPair by document register, not by novelty. \"Best For\" is a prompt, not a decree; a pairing outside this table is fine if it fits — these 8 are seeds, not the set.\n\n| Header | Body | Best For |\n|---|---|---|\n| Georgia | Calibri | Formal business, finance, executive reports |\n| Arial Black | Arial | Bold marketing, product launches |\n| Calibri | Calibri Light | Clean corporate, minimal design |\n| Cambria | Calibri | Traditional professional, legal, academic |\n| Trebuchet MS | Calibri | Friendly tech, startups, SaaS |\n| Impact | Arial | Bold headlines, event decks, keynotes |\n| Palatino | Garamond | Elegant editorial, luxury, nonprofit |\n| Consolas | Calibri | Developer tools, technical / engineering |\n\nSet both fonts explicitly on every shape (`--prop font=Georgia` on titles, `--prop font=Calibri` on body), not via theme inheritance.\n\n### Color and contrast\n\nThe columns: **Primary** (dominant — 60–70% of weight, the color you see first), **Secondary** (supporting tone), **Accent** (sparing, one-hit emphasis), **Text** (body on light fills), **Muted** (captions / axis labels / footer).\n\n| Theme | Primary | Secondary | Accent | Text | Muted |\n|---|---|---|---|---|---|\n| Coral Energy | `F96167` | `F9E795` | `2F3C7E` | `333333` | `8B7E6A` |\n| Midnight Executive | `1E2761` | `CADCFC` | `FFFFFF` | `333333` | `8899BB` |\n| Forest & Moss | `2C5F2D` | `97BC62` | `F5F5F5` | `2D2D2D` | `6B8E6B` |\n| Charcoal Minimal | `36454F` | `F2F2F2` | `212121` | `333333` | `7A8A94` |\n| Warm Terracotta | `B85042` | `E7E8D1` | `A7BEAE` | `3D2B2B` | `8C7B75` |\n| Berry & Cream | `6D2E46` | `A26769` | `ECE2D0` | `3D2233` | `8C6B7A` |\n| Ocean Gradient | `065A82` | `1C7293` | `21295C` | `2B3A4E` | `6B8FAA` |\n| Teal Trust | `028090` | `00A896` | `02C39A` | `2D3B3B` | `5E8C8C` |\n| Sage Calm | `84B59F` | `69A297` | `50808E` | `2D3D35` | `7A9488` |\n| Cherry Bold | `990011` | `FCF6F5` | `2F3C7E` | `333333` | `8B6B6B` |\n\nPick by topic, not by default — finance reads Midnight Executive, a product launch reads Coral Energy, safety / LOTO reads Cherry Bold. If the closest named theme is not quite right, blend (e.g. Forest primary + gold `D4A843` accent). Use **Text** on light fills, **Muted** for captions / axis / footer, `FFFFFF` or Secondary for body on dark fills.\n\nOn dark backgrounds, text and chart series follow the Hard rules contrast floor above.\n\n### Chart-choice decision table\n\nWrong chart type kills the 3-second test:\n\n| Data shape | Use | Avoid |\n|---|---|---|\n| Category comparison (A vs B vs C) | `column` (vertical) / `bar` (≥ 6 categories, horizontal) | pie (slices merge), line (no time axis) |\n| Time series, 1–3 series | `line` | area (occlusion), bar (implies discrete) |\n| Part-of-whole, 2–5 slices | `pie` / `doughnut` | pie with 8+ slices (unreadable) |\n| Correlation / distribution | `scatter` | line (implies ordering) |\n| Multiple categories × metrics, dense | stacked `column` or heatmap | one chart per metric — consolidate |\n| KPI snapshot (single big number) | **Large-text shape** (60–72pt + ≤ 5-word sublabel), NOT a chart | gauge chart, tiny bar |\n\nRule of thumb: if > 3 series and > 8 categories, split into two charts or switch to a table.\n\n### Animation\n\nUse as much or as little as the brand and content call for — a formal finance deck trends to near-zero, a product launch can be more expressive. Animation is a tool, not décor. Three floors keep it from hurting the deck (none caps how much you use):\n\n- **Purposeful** — each one reveals or emphasizes (progressive bullet reveal, a build-up chart), never decorates. If it doesn't aid comprehension, cut it.\n- **Degrades gracefully** — pptx animation renders inconsistently across viewers (Keynote / Slides / web / mobile) and may not play at all, so every slide must read correctly as a *static* frame. Never hide essential content behind a reveal.\n- **Verify live** — animation is runtime-only; `view html` and screenshots can't see it, so confirm in a real presentation viewer before shipping.\n\nTaste steer (not a ban): `fade` / `appear` / a single `zoom-entrance` with snappy durations (~hundreds of ms) fit most decks; `bounce` / `swivel` / `spin` / `fly-from-edge` / dense multi-object choreography usually read amateur — reach for them only when the brand is deliberately playful.\n\n### Layout patterns & data display\n\nVary layout across slides — repeating the same pattern makes every slide feel identical. These are common building blocks, not the full set — pick one per slide, or build a layout outside the table when the content calls for it:\n\n| Pattern | When to use | Key measurement |\n|---|---|---|\n| **Two-column** (text left, visual right) | Concept + evidence; feature + screenshot | Each col ≈ 14-15cm; gap 1cm |\n| **Icon rows** (icon in filled circle + bold header + description) | Feature lists, benefits, team roles | Icon circle 1.5-2cm; 3-4 rows max |\n| **2×2 or 2×3 grid** (card tiles) | Quadrant analysis, SWOT, option comparison | Gap ≥ 0.76cm; consistent card height |\n| **Half-bleed image** (full left or right half, content overlay on other side) | Hero moments, case study openers | Image 16-17cm wide; content column ≥ 14cm |\n| **Large stat callout** (60-72pt number + ≤5-word sublabel below) | Single KPI, milestone, market size | Use shape, NOT a chart; sublabel 14-16pt muted |\n\n**Data display quick rules:**\n- Comparison columns (before/after, A vs B) beat a table for 2-3 options.\n- Timelines and process flows: numbered step shapes + connectors, not a bullet list.\n\n### Image treatment (only when a slide uses a photo / screenshot / logo)\n\n**Read the image first** (open the file) and choose treatment from what you see — don't place blind from a filename.\n\n- **Full-bleed photo** → size to COVER the region (crop edges), no border.\n- **Screenshot / diagram / logo** → size to FIT (never crop content). A transparent or fit image sits on a contrasting fill — drop a colored rectangle behind it, don't let it float on white.\n- **Text over a photo** → never raw on the image. Put it on a card, or lay a protection scrim between image and text (a dark rectangle at ~50–60% opacity, or a gradient fading from the text edge).\n- Never stretch (distort the aspect ratio); don't overlay text on a busy screenshot.\n- Prefer user-provided images / brand assets; no emoji or self-drawn art unless asked.\n\n### Visual motif commitment\n\nPick ONE distinctive element (rounded image frames, section numbers in filled circles, single-side border band, diagonal accent strips) and carry it to every slide — commit across the whole deck; styling one slide and leaving the rest plain reads as abandoned. A secondary motif is fine only if it doesn't compete with the primary. Declare it in your build plan first: `## Motif: numbered circles in brand color`.\n\n### Visual AI-tells to avoid\n\n- **No decorative underline under slide titles.** A stripe / rule below a heading is the single most common AI-slide tell — use whitespace or a background-color change instead.\n- **No rounded-corner card with a colored left-border accent stripe.** The other classic AI-slide tell — use a solid fill, a top accent band, or whitespace separation instead.\n- **No emoji as iconography** unless the brand uses them — use a shape or a real icon asset.\n\nCopy-level tells live in \"Copy reads human\".\n\n## Common Workflow\n\n1. **Open/save lifecycle.** `officecli open <file>` at the start, `officecli save <file>` at the end to flush your edits to disk. `save` only writes — it leaves the resident warm for any follow-up edit; reach for `officecli close <file>` only when you want to release the resident immediately (a one-shot handoff). Both are always safe — they never error or lose work. Use `batch` for repetitive shape grids. **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (python-pptx, PowerPoint, a renderer, delivery).\n2. **Orient.** New deck: `officecli create \"$FILE\"`. Existing: `officecli view \"$FILE\" outline` first. Never edit blind.\n3. **Title sequence first (plan, don't build yet).** Before creating any slide or shape, write out the full ordered list of slide titles. If someone reading ONLY the titles can't follow the argument, fix the arc now — cheaper in a list than after 14 slides. Pick ONE title grammar — all topic noun-phrases or all action statements, never a mix — and hold it throughout (see \"Copy reads human\").\n4. **Build in display order.** Add slides in audience-view order: cover → agenda → section-1 divider → section-1 content → section-2 divider → … → closing. `--index` on slide add works, but linear append keeps the build script readable and avoids index-arithmetic bugs. **Before final delivery, confirm slide count + narrative arc match your build plan.** Gate 3's order-sanity check catches cases where the cover ends up as slide 11 of 14 instead of slide 1.\n5. **Incremental per slide.** Create slide + background, then title, then supporting shapes / charts / connectors. Always `layout=blank` for custom designs. After each structural op, `get /slide[N] --depth 1` to confirm shape IDs.\n6. **Format to spec.** Per the Requirements table; formatting is deliverable, not polish.\n7. **Save + verify.** `officecli save` flushes the file to disk (or `officecli close` to flush and also end the session). Always open in the target presentation viewer before shipping — chart colors, animations, fonts, and zoom are runtime features `view html` can't render. Full verification in QA below.\n8. **QA — assume there are problems.** Fix-and-verify until a cycle finds zero new issues.\n\n## Quick Start\n\nMinimal viable deck: cover + one content slide + notes. `$FILE` stands in for your filename.\n\n```bash\nFILE=\"deck.pptx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\n# Cover — dark fill, centered title\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" /slide[1] --type shape --prop text=\"FY26 Strategic Review\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center\n\n# Content — white fill, title + body + notes\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" /slide[2] --type shape --prop text=\"Revenue grew 18% YoY\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761\nofficecli add \"$FILE\" /slide[2] --type shape --prop text=\"Enterprise renewals + new EMEA region drove the beat; NRR held at 118%.\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=30cm --prop height=3cm \\\n  --prop font=Calibri --prop size=20 --prop color=333333\nofficecli add \"$FILE\" /slide[2] --type notes --prop text=\"Lead with the 18% beat, preview EMEA.\"\n\nofficecli save \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nShape of every build: open → slide+background → title → body → notes → save → validate.\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` first, `view text` / `get` / `query` once you know where to look.\n\n```bash\nofficecli view \"$FILE\" outline          # slide count + titles\nofficecli view \"$FILE\" annotated        # complete per-slide breakdown with fonts, sizes, tables, charts\nofficecli view \"$FILE\" text --start 1 --end 5   # text dump (includes table cell text)\nofficecli view \"$FILE\" issues           # empty slides, overflow hints\nofficecli view \"$FILE\" stats            # counts + totals (incl. pictures missing alt)\n```\n\n**Inspect one element.** XPath-style paths, 1-based. ALWAYS quote. Prefer `@name=` / `@id=` selectors over positional `[N]` (stable across reorderings). `[last()]` works. Add `--json` for machine output.\n\n```bash\nofficecli get \"$FILE\" \"/slide[1]\" --depth 1              # shape list with IDs and names\nofficecli get \"$FILE\" \"/slide[1]/shape[@name=Title]\"\nofficecli get \"$FILE\" \"/slide[1]/table[1]\" --depth 3     # table rows / cells\n```\n\n**Query across the deck.** CSS-like selectors; operators `=`, `!=`, `~=`, `>=`, `<=`, `[attr]`, `:contains()`, `:no-alt`. `help pptx query` lists queryable element types.\n\n```bash\nofficecli query \"$FILE\" 'shape:contains(\"Revenue\")'\nofficecli query \"$FILE\" 'picture:no-alt'                 # accessibility gap\nofficecli query \"$FILE\" 'shape[fill=1E2761]'             # color match\nofficecli query \"$FILE\" 'shape[width>=10cm]'             # numeric\n```\n\n**`query --json` output schema.** Results wrap in `.data.results[]` — `jq -r '.data.results[0].format.id'`, NOT `.[0].id`. Shape name is `.name`; fill is `.format.fill`; textColor is `.format.textColor`.\n\n**Visual preview (LEAD).**\n\n```bash\nofficecli view \"$FILE\" html                # prints an HTML preview path; Read it for per-slide visual audit (best structural ground truth)\nofficecli view \"$FILE\" svg --start 3 --end 3   # single slide SVG (charts + gradients do NOT render in SVG)\n```\n\n**Reading the output — an expected non-defect:**\n- **`layout=blank` has no title placeholder.** Titles are plain `shape` elements, so `view outline` reporting `(untitled)` is **expected**, not a defect. Use `layout=title` + `placeholder[title]` only when screen-reader outline compatibility matters.\n\n## Creating & Editing\n\nVerbs: `add` / `set` / `remove` / `move` / `swap` / `batch` / `raw-set`. Ninety percent of a deck is slides, shapes, text, a few charts, pictures, connectors.\n\n### Slides and backgrounds\n\nA slide is `/slide[N]`. Always pass `layout=blank` for custom designs. Background: solid, gradient, or image.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761                 # solid\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop \"background=1E2761-CADCFC-180\"   # gradient (start-end-angle)\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=image:/path/to/hero.jpg  # image background (LEAD)\n```\n\n### Shapes\n\nA `shape` holds text, fill, border, position, and optional animation / link.\n\n```bash\nofficecli add \"$FILE\" /slide[2] --type shape --prop name=Title --prop text=\"Key Insight\" \\\n  --prop x=2cm --prop y=2cm --prop width=20cm --prop height=3cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n```\n\nPositioning is explicit — no layout engine, you own the grid math. `--prop preset=` picks geometry (`rect`, `roundRect`, `ellipse`, `triangle`, `arrow`, `star5`, ...); custom `M...Z` paths are not supported — pick a preset. **Name shapes at creation** (`--prop name=HeroTitle`) and address later with `\"/slide[N]/shape[@name=HeroTitle]\"` — names survive z-order / remove-then-add, whereas positional `/shape[3]` (and even `@id=`) shift. Re-`get --depth 1` after any structural change before using positional indexes.\n\n### Text inside shapes (paragraphs, runs, styling)\n\nA shape has paragraphs (`paragraph[K]`) and runs (`run[K]`). For one-line text, `--prop text=` on the shape is enough; a `\\n` in the text makes a paragraph break, `\\t` a tab (see Shell & Execution Discipline; double `\\\\n` for a literal). `add --type paragraph` takes the same style props as a shape (text, align, bold, italic, size, color, font). For mixed styling *within* a line, append a styled run:\n\n```bash\nofficecli add \"$FILE\" \"/slide[2]/shape[@name=Card1]/paragraph[1]\" --type run \\\n  --prop text=\" (inline detail)\" --prop size=14 --prop italic=true --prop color=8899BB\n```\n\n### Charts\n\nPick chart type per the Design Principles chart-choice table. Full prop list (chartType enum, `seriesN.*`, `data=`/`categories=`, axis options): `help pptx add chart`. Typical multi-series with brand colors:\n\n```bash\nofficecli add \"$FILE\" /slide[3] --type chart --prop chartType=column \\\n  --prop series1.name=Revenue --prop series1.values=\"42,45,48\" --prop series1.color=1E2761 \\\n  --prop series2.name=Growth  --prop series2.values=\"2,7,7\"    --prop series2.color=CADCFC \\\n  --prop categories=\"Q1,Q2,Q3\" \\\n  --prop x=2cm --prop y=4cm --prop width=20cm --prop height=10cm\n```\n\nGotchas: (1) chart titles with `()`, `[]`, `TBD` ship as literal text. (2) some viewers normalize chart colors to theme defaults — verify in the target viewer. Series can be added after creation (`add --type series`).\n\n### Pictures\n\n```bash\nofficecli add \"$FILE\" /slide[4] --type picture --prop src=hero.jpg \\\n  --prop x=1cm --prop y=1cm --prop width=32cm --prop height=18cm \\\n  --prop alt=\"Product hero, gradient lit from right\"\n```\n\nConfirm with `officecli query \"$FILE\" 'picture:no-alt'` — must be empty before delivery.\n\n### Connectors (LEAD — flowcharts / decision trees first-class)\n\nDraws a line between two shapes or free coordinates. Full prop / enum reference (`shape`, `headEnd`/`tailEnd` values, `from`/`to` ref forms): `help pptx add connector`.\n\n```bash\nofficecli add \"$FILE\" /slide[5] --type connector \\\n  --prop \"from=/slide[5]/shape[@name=BoxA]\" --prop \"to=/slide[5]/shape[@name=BoxB]\" \\\n  --prop shape=elbow --prop color=333333 --prop tailEnd=triangle\n```\n\n**Every flow connector needs an arrowhead.** Without one, `bentConnector3` renders as a directionless line. `preset=rightArrow` overlay only works for horizontal flows; diamonds / decision trees with diverging edges need `tailEnd=`.\n\n### Animations (LEAD)\n\nUse per the Animation floors above (purposeful, degrades gracefully, verify live). Preset names + duration syntax: `help pptx animation`.\n\n```bash\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=HeroCard]\" --prop animation=fade-entrance-400\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=HeroCard]\" --prop animation=none    # clear all\n```\n\n### Hyperlinks, tooltips, slide-jump\n\n`--prop link=slide[N]` for an in-deck jump (1-based; target slide must exist), `link=nextslide` / `firstslide` / `lastslide` / `previousslide` / `endshow` for named navigation, `link=https://...` for a URL, `--prop tooltip=\"...\"` for hover text.\n\n### Tables, placeholders, groups, zoom — one-liners\n\n- **Tables** — `--type table --prop rows=N --prop cols=M`. Row-level `set` supports `height` and `c1/c2/c3` (seed cell text). Header-row styling is table-level (`firstRow=true` / `headerFill=`), not a row prop. Cell formatting lives on the cell paragraph / run. Populate rows BEFORE setting table-level font (font cascade gets reset by row ops).\n- **Placeholders** — `\"/slide[N]/placeholder[title]\"` / `placeholder[body]`. Available only when the slide uses a layout with placeholders (not `layout=blank`).\n- **Groups** (LEAD) — address children via `\"/slide[N]/group[@name=G]/shape[1]\"`. Survives reordering better than positional indexes.\n- **Zoom slide** (LEAD) — `--type zoom --prop target=N` (one link per target; alias `slide`). Emit N separate zoom shapes for a multi-target nav hub. Zoom is a runtime feature — `view html` shows the static geometry; the zoom interaction runs only in a live presentation viewer.\n- **Slide comments** — reviewer annotations anchored at `/slide[N]/comment[M]`. Full lifecycle (`add / set / get / query / remove`). Props: `text`, `author`, `initials` (auto-derived), `date` (ISO 8601, defaults to UtcNow), `x` / `y` (length anchor).\n  ```bash\n  officecli add \"$FILE\" \"/slide[2]\" --type comment --prop author=\"Alice\" --prop text=\"Tighten this bullet\" --prop x=20cm --prop y=3cm\n  officecli query \"$FILE\" 'comment' --json | jq '.data.results | length'   # count all review comments\n  officecli remove \"$FILE\" \"/slide[2]/comment[1]\"                           # resolve after addressing\n  ```\n\n### Deck-level recipes\n\nPatterns not obvious from the primitives. Each gives the **visual outcome** first, then a runnable block. `$FILE` = your filename. Use `/slide[last()]` to address the slide you just added. The recipes demonstrate **structure and coordinate math** — swap in the palette / fonts you chose for this topic; the navy `1E2761` + Georgia is just the example's theme, not a house style to copy verbatim.\n\n**Z-order.** Later-added shapes are on top. Add background decoration FIRST, titles LAST. To fix after the fact: `--prop zorder=back/front` (renumbers siblings — re-`get --depth 1` before stacking more).\n\n#### (a) Cover (and section divider)\n\n**Visual outcome.** Dark navy fill, centered 44pt title, 18pt ice-blue meta line.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Strategic Growth Review\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Prepared for Acme Leadership — FY26 Outlook\" \\\n  --prop x=2cm --prop y=11cm --prop width=29.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=18 --prop color=CADCFC --prop align=center\n```\n\n**Section divider** = same cover, plus a giant translucent number (`size=120`, `opacity=0.15`) added FIRST so it sits behind the section title.\n\n#### (b) Data slide (chart + commentary block)\n\n**Visual outcome.** Left two-thirds: column chart with brand series colors. Right one-third: \"Key Insight\" card with 20pt heading + 18pt body — audience reads the takeaway before parsing the bars.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"FY26 Revenue Beat Plan by 18%\" \\\n  --prop x=1.5cm --prop y=1cm --prop width=30cm --prop height=1.8cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761\n\n# Chart — left 2/3 (single-quote the title because of `$`)\nofficecli add \"$FILE\" \"/slide[last()]\" --type chart --prop chartType=column \\\n  --prop series1.name=Actual --prop series1.values=\"42,45,48,55\" --prop series1.color=1E2761 \\\n  --prop series2.name=Plan --prop series2.values=\"40,42,45,48\" --prop series2.color=CADCFC \\\n  --prop categories=\"Q1,Q2,Q3,Q4\" --prop x=1.5cm --prop y=3.5cm --prop width=20cm --prop height=14cm --prop title='FY26 Revenue ($M)'\n\n# Commentary card — right 1/3: background + heading + body\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop preset=roundRect --prop fill=F5F7FA --prop line=none \\\n  --prop x=22.5cm --prop y=3.5cm --prop width=9.8cm --prop height=14cm\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Key Insight\" \\\n  --prop x=23cm --prop y=4cm --prop width=9cm --prop height=1.2cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"EMEA launch + NRR at 118% drove 12pp of the 18pp beat.\" \\\n  --prop x=23cm --prop y=5.5cm --prop width=9cm --prop height=11cm \\\n  --prop font=Calibri --prop size=18 --prop color=333333\n```\n\n#### (c) Flowchart / process diagram (boxes + connectors)\n\n**Visual outcome.** Four rounded boxes across at y=8cm, each 6×3cm, alternating navy/iceblue, joined by elbow connectors with triangle arrowheads.\n\nGrid math (4 boxes, 33.87cm slide, 1.5cm margins): `gap = (33.87 − 3 − 24) / 3 = 2.29cm`. x-positions: `1.5, 9.79, 18.08, 26.37`.\n\nEach box carries its own label via `valign=middle` (no separate overlay shape needed). Use `batch` heredoc for portable coordinate arithmetic — no `bc`, no bash arrays.\n\n```bash\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step1\",\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"1.5cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 1\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step2\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"9.79cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 2\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step3\",\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"18.08cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 3\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step4\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"26.37cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 4\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}}\n]\nEOF\n\n# Connector pattern — reuse for any box-to-box graph.\nfor pair in \"Step1 Step2\" \"Step2 Step3\" \"Step3 Step4\"; do\n  A=${pair% *}; B=${pair#* }\n  officecli add \"$FILE\" \"/slide[$SLIDE]\" --type connector \\\n    --prop \"from=/slide[$SLIDE]/shape[@name=$A]\" \\\n    --prop \"to=/slide[$SLIDE]/shape[@name=$B]\" \\\n    --prop shape=elbow --prop color=333333 --prop tailEnd=triangle\ndone\n```\n\n`shape=elbow` is canonical (`bentConnector2` / `bentConnector3` also accepted).\n\n#### (d) Multi-slide deck skeletons\n\nNo code block — it's a rhythm. The sequences below are **illustrations of one working cadence (alternating dark dividers with white content), not required running orders** — derive your actual arc from the content first (see \"Title sequence first\"), then borrow whatever divider/content rhythm fits:\n\n- **10-slide review:** Cover · Agenda · 3 KPI · Div01 · Chart · Chart · Div02 · Flow · Timeline · Close\n- **20-slide pitch:** same rhythm × 2, sectioned Problem · Solution · Market · Product · Traction · Model · Team · Financials · Ask\n- Every divider must appear **before** its section content (Gate 3 order sanity)\n- Cover/divider = (a); chart pages = (b); process pages = (c); KPI pages = (e); decision pages = (f)\n\n#### (e) KPI callouts — giant-number card grid\n\n**Visual outcome.** Three or four giant numbers across a row; each card = unit sublabel + small percent-change chip + one-line takeaway. The single most common exec-deck element.\n\n**Sizing rule.** 60pt Georgia bold fits ~5 chars in a 9.78cm card (`$84.2`, `118%`, `24.5`). For longer values (`$84.2M`), split: `$84.2` as the big number, `USD millions` as the sublabel — never shrink the font to chase a unit suffix, it just wraps.\n\nGrid math (3 cards, 1.5cm margins, 0.76cm gap): `col_width = (33.87 − 3 − 1.52) / 3 = 9.78cm`. x-positions: `1.5, 12.04, 22.58`. Use accent color on a single \"watch\" card so risk reads in one second.\n\n```bash\n# Two cards: navy standard + terracotta watch. Each = bg + big number + sublabel + chip.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"1.5cm\",\"y\":\"4cm\",\"width\":\"9.78cm\",\"height\":\"7cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"84.2\",\"x\":\"1.5cm\",\"y\":\"4.8cm\",\"width\":\"9.78cm\",\"height\":\"2.8cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"USD millions · ARR\",\"x\":\"1.5cm\",\"y\":\"8cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"color\":\"CADCFC\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"+24% YoY\",\"x\":\"1.5cm\",\"y\":\"9cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"CADCFC\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"preset\":\"roundRect\",\"fill\":\"B85042\",\"line\":\"none\",\"x\":\"22.58cm\",\"y\":\"4cm\",\"width\":\"9.78cm\",\"height\":\"7cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"\\$1.42\",\"x\":\"22.58cm\",\"y\":\"4.8cm\",\"width\":\"9.78cm\",\"height\":\"2.8cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"CAC payback (yrs)\",\"x\":\"22.58cm\",\"y\":\"8cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"+8% — watch\",\"x\":\"22.58cm\",\"y\":\"9cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}}\n]\nEOF\n```\n\n#### (f) Decision tree — YES/NO branching\n\n**Visual outcome.** Diamond at top-center; YES/NO child boxes diverging left-right; both converge into a shared terminal box. Layout: diamond at `x=13.94, y=2cm, 6×3cm`; YES at `3cm, 7.5cm`; NO at `22.87cm, 7.5cm`; terminal at `13.94cm, 13cm`. Convention: red = stop/escalate, blue = standard, green = safe terminal. **Every connector needs an arrowhead** — readers misparse direction otherwise.\n\n```bash\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Decide\",\"preset\":\"diamond\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"13.94cm\",\"y\":\"2cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Hazardous energy present?\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"YesBox\",\"preset\":\"roundRect\",\"fill\":\"B85042\",\"line\":\"none\",\"x\":\"3cm\",\"y\":\"7.5cm\",\"width\":\"8cm\",\"height\":\"3cm\",\"text\":\"Lockout + Tagout + Verify\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"NoBox\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"22.87cm\",\"y\":\"7.5cm\",\"width\":\"8cm\",\"height\":\"3cm\",\"text\":\"Proceed with standard PPE\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Done\",\"preset\":\"roundRect\",\"fill\":\"2C5F2D\",\"line\":\"none\",\"x\":\"13.94cm\",\"y\":\"13cm\",\"width\":\"6cm\",\"height\":\"2.5cm\",\"text\":\"Begin service\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}}\n]\nEOF\n```\n\nThen 4 connectors (`Decide→YesBox`, `Decide→NoBox`, `YesBox→Done`, `NoBox→Done`) using the connector loop pattern from (c).\n\n## QA (Required)\n\n**Assume there are problems.** First render is almost never correct. If you found zero issues, you were not looking hard enough.\n\n### Delivery Gate (any failure = REJECT, do NOT deliver)\n\nGates 1–2b are text/schema-level (cannot see a rendered slide); Gate 3 is the only visual check. Done = every gate PASS **and** Gate 3 loop converged.\n\nEach gate is **run a command, judge its output** — the officecli commands are identical on every OS (macOS / Linux / Windows), so no shell scripting is needed; the judging is yours.\n\n- **Gate 1 — schema.** `officecli validate \"<file>\"`. Any schema error → REJECT and fix.\n- **Gate 2 — overflow / format / structure.** `officecli view \"<file>\" issues`. If it lists *any* issue (lines tagged `[O1]`, `[C1]`, `[S1]`, …) → REJECT, fix, re-run until clean.\n- **Gate 2b — leftover placeholders.** `officecli view \"<file>\" text`, then scan the output for `xxxx`, `lorem` / `ipsum`, `<TODO>`, `placeholder`, \"this slide layout\", or empty `()` / `[]`. Any hit → REJECT.\n\n### Gate 3 — Visual audit (MANDATORY)\n\nPick **one** path:\n\n**Screenshot (default)** — for vision-capable agents. Screenshot each slide in turn — `officecli view \"<file>\" screenshot --page 1 -o slide1.png`, then `--page 2`, … — until the page index runs past the deck (one screenshot = one slide). If it errors on page 1, use the fallback below.\n\n**Judge every PNG against the checklist, adversarially** — \"assume problems exist; finding none means you didn't look hard enough.\" Report one `slide N: <issue>` line per problem, or `PASS`. This step is required however you run it. **If** your harness can spawn a subagent, delegate the judging to a *fresh, independent* one — the agent that built the deck is biased toward \"looks fine\", a separate pair of eyes is more critical — handing it the screenshots + this checklist and the same adversarial framing. No subagent? Do exactly the same yourself.\n\n**Fallback — HTML-text** (no vision, or screenshot failed): read `view \"$FILE\" html` as text. DOM cannot prove **dark-on-dark / fine overlap / arrowheads / gap-margin metrics / column alignment** — flag these as \"not visually verified\" rather than PASS.\n\n**Optional `--grid N`** — only on user request for layout-rhythm, or when `view outline` shows anomalous layout distribution: `officecli view \"<file>\" screenshot --grid 3 -o grid.png`.\n\n**Per-slide checklist (assume issues exist):**\n\n- **overlap** — shapes / charts / giant decorative numbers (01/02/03 100pt+) colliding\n- **text overflow** — clipped at slide or shape boundary (KPI cards, narrow boxes)\n- **narrow text box** — content fits technically but wraps to many short lines (1–2 words each); long sublabel in a 3cm KPI card, body line in a too-tight column\n- **dark-on-dark** — fill brightness < 30% with text/icon brightness < 80% (incl. dark icons on dark without a contrasting circle)\n- **image treatment** — photo stretched/distorted, text raw on a busy image (no card/scrim), screenshot or logo cropped, transparent image floating on white\n- **missing arrowheads** — flowchart connectors as plain lines\n- **decorative-line / title mismatch** — accent bar sized for one-line title but title wrapped to two (or vice versa)\n- **footer / citation collision** — source line, page number, or footnote touching content above\n- **tight margin / gap** — element within ~0.5\" of slide edge, or two cards within ~0.3\"\n- **uneven gaps** — large empty area on one side, cramped on another (broken rhythm)\n- **column / repeat-element misalignment** — KPI cards / icons off baseline or inconsistent width\n- **order sanity** — sequence matches narrative (cover → agenda → dividers-before-sections → closing)\n\nREJECT with `slide N: <issue>` lines, else \"Gate 3 PASS\" (HTML-text fallback adds \"<unverified-items> not visually verified\").\n\n**Fix-verify (mandatory, max 3 cycles).** Fix → re-run Gate 3 → repeat until zero new issues; one fix often surfaces another. After 3 rounds without convergence, **stop** — likely seesaw, template-level cause, or agent misread. Report `slide N: <issue> — attempted: <fixes> — likely root: <template|design-conflict|ambiguous>` and let the user decide.\n\n**Then flush (part of the gate).** Once Gate 3 converges, end with `officecli save \"<file>\"` — this guarantees your edits are written to disk before delivery (use `officecli close \"<file>\"` instead to also release the resident on a one-shot handoff). Required final step, not optional. Always safe: never errors or loses work.\n\n## Common Pitfalls\n\nSanity-check cheatsheet — what breaks on the first try. Design + shell traps.\n\n| Pitfall | Correct approach |\n|---|---|\n| Unquoted `[N]` in zsh/bash | Always quote paths: `\"/slide[1]\"`. zsh globs unquoted `[1]` → `no matches found` — #1 first-use stumble |\n| `--name \"foo\"` | All attributes go through `--prop`: `--prop name=\"foo\"` |\n| `/shape[myname]` (bare name in brackets) | Use `@name=` selector: `/shape[@name=myname]` or `/shape[@id=10007]` |\n| Paths 1-based vs `--index` 0-based | `/slide[1]` = first slide; `--index 0` = first position |\n| `$` in `--prop text=` | Single-quote: `--prop text='$15M'`. Double-quoted `\"$15M\"` gets shell-expanded to `M` |\n| `\\n` / `\\t` in `--prop text=` | Interpreted by the CLI: `\\n` = paragraph break, `\\t` = tab. Double `\\\\n` for a literal |\n","skills/officecli-word-form/SKILL.md":"---\nname: officecli-word-form\ndescription: \"Use this skill to create fillable Word forms (.docx) with real Content Controls (SDT) + legacy FormField checkboxes + MERGEFIELD mail-merge placeholders + document protection. Trigger on: 'fillable form', 'form fields', 'content controls', 'SDT', 'word form', 'fill in', 'only editable fields', 'protect document', 'onboarding form', 'HR intake', 'survey template', 'contract / SOW template', 'mail-merge template', 'compliance checklist', 'medical intake questionnaire'. Output is a single .docx where specific fields are editable and the rest is locked. This skill is INDEPENDENT, not a scene layer on docx — payload is `w:sdt` + `w:ffData` + `w:fldChar` + `documentProtection`, none of which docx base skill covers. Do NOT trigger for regular reports, letters, memos, academic papers, pitch decks, or any document with no user-fillable fields — route those to officecli-docx or its scene layers.\"\n---\n\n# OfficeCLI Word-Form Skill\n\n**This skill is INDEPENDENT, not a scene layer on docx.** A form's payload — `<w:sdt>` controls, `<w:ffData>` legacy fields, `<w:fldChar>` mail-merge, `documentProtection` — is a distinct element class from docx's paragraph/heading/style primitives. Its QA is different too: docx's Delivery Gate cares about visual layout and live PAGE fields, this skill's cares about data plumbing (protection enforced / alias+tag / items injected / name ≤ 20 / no underscore anti-pattern). **Reverse handoff:** if the user's document has no fillable fields (report, letter, memo, thesis, proposal), route to `officecli-docx` or a docx scene skill — don't use this one.\n\n## BEFORE YOU START (CRITICAL)\n\n**If `officecli` is not installed:**\n\n`macOS / Linux`\n\n```bash\nif ! command -v officecli >/dev/null 2>&1; then\n    curl -fsSL https://d.officecli.ai/install.sh | bash\nfi\n```\n\n`Windows (PowerShell)`\n\n```powershell\nif (-not (Get-Command officecli -ErrorAction SilentlyContinue)) {\n    irm https://d.officecli.ai/install.ps1 | iex\n}\n```\n\nVerify: `officecli --version`\n\nIf `officecli` is still not found after first install, open a new terminal and run the verify command again.\n\nIf the install command above fails (e.g. blocked by security policy, no network access, or insufficient permissions), install manually — download the binary for your platform from https://github.com/iOfficeAI/OfficeCLI/releases — then re-run the verify command.\n\n## Help-First Rule\n\nThis skill teaches what a real form needs, not every CLI flag. When a prop / alias / enum is uncertain, consult help BEFORE guessing: `officecli help docx [element] [--json]` (e.g. `sdt`, `formfield`, `field`). Help is pinned to the installed CLI version and is authoritative — when this skill and help disagree, **help wins** (the prop set on `sdt` in particular has grown over time; trust `help docx sdt`, not a hardcoded list).\n\n## Mental Model & Inheritance\n\nA Word form is a `.docx` plus four OpenXML payload layers plain-docx skills do not touch: **`<w:sdt>`** content controls (types: text / richtext / dropdown / combobox / date / picture / group), **`<w:ffData>`** legacy FormField (still the only way to get a real checkbox — SDT `type=checkbox` is not implemented), **`<w:fldChar>`** complex fields (MERGEFIELD, REF, PAGEREF, SEQ, IF — template-time, not user-fill), and **`documentProtection`** (the lock that makes non-field text read-only in Word — and, on the CLI, `protection=forms` locks non-field content edits (those need `--force` or `raw-set`) but still allows form-field (SDT) edits, which is the point of forms protection).\n\n**No inheritance from docx v2.** docx's Delivery Gate (cover-fill %, live-PAGE check) does NOT apply — form QA is `view forms` + `query sdt alias+tag` + `protectionEnforced`.\n\n**Reverse handoff to docx.** Route back to `officecli-docx` for reports / letters / memos / thesis / pitch decks / any document with no editable fields. Use **this** skill when the document's purpose is data capture or template merge.\n\n## Shell & Execution Discipline\n\n**One command at a time. Read output before the next.** OfficeCLI is incremental — every `add` / `set` / `remove` immediately mutates the file. All recipes below use `FILE=form.docx` as a shell variable.\n\n**Three shell-escape layers:**\n\n1. **Quote every path with `[N]`** — zsh/bash glob-expand brackets. `officecli get \"$FILE\" /body/sdt[1]` fails with `no matches found`. Correct: `officecli get \"$FILE\" '/body/sdt[1]'`.\n2. **Single-quote any prop containing `$`** — `\"Total: $50,000\"` becomes `\"Total: ,000\"` after `$50` variable expansion. Correct: `'Total: $50,000'`.\n3. **`--after find:<text>` uses outer single quotes, never inner double quotes** — `--after find:\"Client Signature:\"` makes the quotes part of the search string; match fails. Correct: `--after 'find:Client Signature:'`.\n\n**`WARNING: UNSUPPORTED` (exit 2) is a silently-wrong element.** The CLI created the element *without* the rejected prop (exit 2 = ran with caveats; only when *every* prop is refused does `--json` report `success: false`). Any UNSUPPORTED in your build log means a prop name the current CLI does not accept on that element — stop, check `help docx <element>` for the right prop name (most SDT props such as `items`/`format`/`lock` ARE accepted now; `maxlength` is not), fix the command, and re-run. Do not ship on top.\n\n**`protection=forms` is the LAST structural command.** Once it is set, `protection=forms` locks non-field content (those edits need `--force` or `raw-set`) but still allows form-field (SDT) edits — which is the point of forms protection. A non-field content edit (e.g. `set /body/p[N] --prop text=`) is refused (`ERROR: Document is protected … use --force`); a form-field edit (e.g. `set /body/sdt[N] --prop text=` / `--prop alias=`) succeeds (exit 0). Use `Query(\"editable\")` to find fillable fields. So finish all non-field (static layout) edits first, then lock; if you must edit static content afterward, pass `--force` (or temporarily clear protection, edit, re-lock).\n\n### `--after find:` micro-playbook\n\n`--after find:<text>` matches the **first** occurrence. Bad anchor = wrong insertion location, expensive to debug. Three rules:\n\n1. **Anchor must be globally unique.** In bilingual contracts \"甲方签字\" matches both parties — use a unique phrase like \"甲方签字（Service Provider）\" or full English title.\n2. **After insert, `/body/p[last()]` is unreliable** — the find insertion changes `<w:body>` child order. To continue operating on the new paragraph, read its real paraId: `officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId'`.\n3. **Chinese + full-width parens `（）`** match literally in `find`, but when unsure, `officecli view \"$FILE\" text | grep -n \"锚点\"` first to confirm the exact bytes in the file.\n\n```bash\n# Trap: first-match hits 甲方 only, 乙方 missed\nofficecli add \"$FILE\" /body --type sdt --after 'find:签字'\n\n# Fix: two signatories, two unique anchors\nofficecli add \"$FILE\" /body --type sdt --prop alias=Party_A_Name --prop tag=party_a \\\n  --after 'find:甲方签字（Service Provider）'\nPID_A=$(officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId')\nofficecli add \"$FILE\" \"/body/p[@paraId='$PID_A']\" --type sdt --prop alias=Party_A_Title --prop tag=party_a_title\n```\n\nInline SDT via `--after find:` is added as a child of the matched paragraph, not as a new paragraph — use this when label + SDT must share a line.\n\n## What makes a real form (identity)\n\nA real fillable form requires **structured fields** + **document protection**.\n\n| Approach | Word user sees | CLI-readable | Real form? |\n|---|---|---|---|\n| SDT controls + `protection=forms` | Gray-bordered fields; rest locked | `query sdt` / `view forms` | **YES** |\n| FormField checkbox + `protection=forms` | Real clickable checkbox; rest locked | `query formfield` / `view forms` | **YES** (checkbox only) |\n| MERGEFIELD placeholders | `«CustomerName»` merged by downstream engine | `query field` | **YES** (template-time) |\n| Underscores `___` / blank lines | Visual-only; whole doc editable | No — no structured fields | **NO** |\n\n**Do not simulate fields with underscores.** `姓名：_______________` produces zero structured data and leaks past every verification. Always use `--type sdt` or `--type formfield`.\n\n**Checkbox is formfield, NOT SDT.** `--type sdt --prop type=checkbox` exits 1 (`SDT type 'checkbox' is not implemented`). Every checkbox in every recipe uses `--type formfield --prop type=checkbox`.\n\n**MERGEFIELD is a separate track.** `view forms` lists SDT + formfield only; `query field` lists complex fields only. Two disjoint inventories; both valid in one file.\n\n## Requirements for Outputs (hard floor)\n\nEvery form must satisfy these — Delivery Gate enforces each as an executable check.\n\n1. `protection=forms` enforced (`get $FILE /` → `protectionEnforced=True`).\n2. Every SDT has both `alias` + `tag`.\n3. Every dropdown/combobox has non-empty `items=...` in `view forms`.\n4. Every date SDT shows the intended `format=...`.\n5. Every locked SDT shows `lock=sdtLocked` / `contentLocked` / `sdtContentLocked` as intended.\n6. Zero `WARNING: UNSUPPORTED` in build log.\n7. Zero `type=checkbox` on any SDT.\n8. Every formfield `name` ≤ 20 characters.\n9. Zero underscore-line / blank-line placeholders.\n10. Field types match user intent (short text / paragraph / fixed list / list+custom / date / boolean).\n\n## Three Paths (core decision)\n\nSDT props are **first-class on `add`** — confirm the current set with `officecli help docx sdt`. Today that includes `type, tag, alias, text, items, format, lock, placeholder/placeholderText, date.*` and the SDT types `text / richtext / dropdown / combobox / date / group / picture`. So most forms are pure `add` — no raw-set, no Word template. Two paths remain only for the genuinely-unreachable cases. **Pick the path before writing a single command.**\n\n### Path A — Pure CLI (the default for almost everything)\n\n**Use when**: any text / richtext / dropdown / combobox / date / picture / group SDT — including its options, date format, and lock. Pass the props straight on `add`; verify each persists with `get '/body/sdt[N]' --json`.\n\n```bash\n# dropdown WITH its options and a non-default date format, in one add each — no raw-set:\nofficecli add \"$FILE\" /body --type sdt \\\n  --prop type=dropdown --prop alias=\"Department\" --prop tag=dept \\\n  --prop items=\"Engineering,Finance,HR\"\nofficecli add \"$FILE\" /body --type sdt \\\n  --prop type=date --prop alias=\"Start Date\" --prop tag=start \\\n  --prop format=\"yyyy年MM月dd日\"\n# lock works on add AND set:\nofficecli add \"$FILE\" /body --type sdt --prop type=text --prop tag=full_name \\\n  --prop alias=\"Full Name\" --prop text=\"Enter full name\" --prop lock=sdtLocked\n# protection comes last, once all fields exist:\n# officecli set \"$FILE\" / --prop protection=forms\n```\n\n### Path B — CLI + `raw-set` bridge (only an attribute help does NOT expose)\n\n**Use when**: you need an SDT attribute that `help docx sdt` does not list (e.g. a `<w:listItem>` whose `w:value` must differ from its display text, or a sdtPr child with no `--prop`). `raw-set` is OfficeCLI's universal OpenXML fallback — `officecli --help` lists it as a top-level command. For ordinary dropdowns use `--prop items=` (Path A); raw-set here is the exception, not the rule.\n\n```bash\n# Display text ≠ stored value — not expressible via items=, so inject the listItems directly:\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown --prop alias=\"Department\" --prop tag=dept\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"ENG\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"FIN\"/>'\n```\n\n### Path C — Word template (only what no API reaches)\n\n**Use when**: a **real SDT checkbox** (`type=checkbox` still exits 1 — use a legacy FormField instead, see §Legacy FormField), a `placeholderDocPart` prompt-text part, or custom richtext appearance / cross-part nesting beyond `--prop` reach. Picture and grouped SDTs are NO LONGER here — they add fine via Path A.\n\n```bash\n# One-time in Word: Developer tab → Insert Content Control → Save as template.docx\ncp templates/onboarding_with_signature.docx \"$FILE\"\nofficecli open \"$FILE\"\nofficecli view \"$FILE\" forms                 # inspect embedded controls + paths\nofficecli set \"$FILE\" '/body/sdt[@sdtId=3]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" / --prop protection=forms\n```\n\n### Decision table\n\n| Need | Path | Note |\n|---|---|---|\n| text / richtext SDT with default string | **A** | `--prop type/alias/tag/text` |\n| text SDT that must be locked | **A** | `--prop lock=sdtLocked` works on `add` (and `set`) |\n| dropdown / combobox **with options** | **A** | `--prop items=\"A,B,C\"` |\n| date SDT with non-default format | **A** | `--prop format=\"yyyy年MM月dd日\"` |\n| signature picture SDT, grouped SDT | **A** | `--prop type=picture` / `type=group` add directly |\n| dropdown whose stored value ≠ display text | **B** | raw-set append `<w:listItem w:value=…>` |\n| real checkbox | **FormField** | `--type formfield --prop type=checkbox` (see §Legacy FormField) |\n| mail-merge placeholder | **MERGEFIELD** | `--type field --prop fieldType=mergefield` (see §MERGEFIELD) |\n| real SDT checkbox / placeholder part / custom appearance | **C** | build skeleton in Word, fill via CLI |\n\n## Quick Start — Path A + FormField (minimal intake form)\n\nTwo SDT text fields, one checkbox, protection. Paste and adapt; this is the smallest form worth shipping.\n\n```bash\nFILE=intake.docx\nofficecli close \"$FILE\" 2>/dev/null; rm -f \"$FILE\"   # preflight: clear stale resident / prior file (cold-start after CLI upgrade commonly leaks a resident)\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\nofficecli set \"$FILE\" / --prop title=\"Employee Onboarding Intake\" \\\n  --prop docDefaults.font=\"Calibri\" --prop docDefaults.fontSize=\"12pt\"\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Employee Onboarding Intake\" --prop style=Heading1 \\\n  --prop size=20 --prop bold=true --prop spaceAfter=18pt\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Full Name:\" --prop size=11 --prop bold=true --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Full Name\" --prop tag=full_name --prop text=\"Enter full name\"\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Start Date:\" --prop size=11 --prop bold=true --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Start Date\" --prop tag=start_date\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Read and agree to employee handbook\" --prop size=11 --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type formfield \\\n  --prop type=checkbox --prop name=agree_handbook --prop checked=false\n\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\nofficecli view \"$FILE\" forms\n```\n\n## Path B — raw-set recipes\n\nThree recipes cover almost every complex-attr need on SDT forms.\n\n### B1 — Dropdown items (append)\n\n```bash\n# Skeleton (Path A)\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown \\\n  --prop alias=\"Department\" --prop tag=dept\n\n# Inject items\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"Engineering\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"Finance\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"HR\" w:value=\"HR\"/>'\n\n# Verify\nofficecli get \"$FILE\" '/body/sdt[1]'   # expect: type=dropdown items=Engineering,Finance,HR\n```\n\n**Template.** Swap `<TAG>` / `<LABEL>` / `<VALUE>` only. `xmlns:w=...` is required on every root `<w:listItem>` — raw-set does not inherit namespace prefixes. Chain multiple `<w:listItem>`s in one call; option order is preserved.\n\n### B2 — Combobox items (same as B1, different xpath tail)\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=combobox \\\n  --prop alias=\"Current Medication\" --prop tag=current_med\n\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='current_med']/w:sdtPr/w:comboBox\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Antihypertensives\" w:value=\"Antihypertensives\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Insulin\" w:value=\"Insulin\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Other (specify)\" w:value=\"Other\"/>'\n```\n\nOnly difference from B1: `w:comboBox` vs `w:dropDownList` in the xpath tail. Combobox lets the user type custom input; dropdown does not.\n\n### B3 — Date format (setattr)\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Contract Start Date\" --prop tag=contract_start\n\n# Chinese: yyyy年MM月dd日\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='contract_start']/w:sdtPr/w:date/w:dateFormat\" \\\n  --action setattr \\\n  --xml \"w:val=yyyy年MM月dd日\"\n\n# US:    w:val=MM/dd/yyyy\n# ISO:   w:val=yyyy-MM-dd  (already the default)\n# Long:  w:val=\"MMMM d, yyyy\"\n\nofficecli get \"$FILE\" '/body/sdt[N]'   # expect: type=date format=yyyy年MM月dd日\n```\n\n`setattr` replaces one attribute — do not quote the value inside `--xml`. Only `w:val` is touched; the `<w:dateFormat>` wrapper is preserved.\n\n### raw-set actions & errors\n\n| `--action` | Form use |\n|---|---|\n| `append` | Insert new child at end of target (B1, B2 — listItem) |\n| `setattr` | Change one attribute; `--xml \"key=value\"` (B3 — dateFormat/@val) |\n| `replace` | Replace entire target (rare — reset a full `<w:date>` wrapper) |\n| `remove` | Delete the target (clear options before re-populate) |\n\n| Symptom | Fix |\n|---|---|\n| `raw-set: 0 element(s) affected` | XPath did not match. Check the `tag` value and whether the SDT is block or inline. Fall back to `officecli raw $FILE /document` to read the real XML. |\n| `Error: prefix 'w' is not defined` | Missing `xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"` on the fragment — every root element in `--xml` needs it. |\n| Items readback empty after append | `<w:dropDownList/>` must already exist (Path A `type=dropdown` ensures this). If absent, append has nowhere to insert. |\n| `VALIDATION: N new error(s) introduced` on same line as success | Your append introduced a schema-invalid child. Treat as stop-and-fix even though `raw-set` exits 0. |\n\n## Path C — Word template workflow\n\nFor fields CLI cannot express (signature `picture` SDT, real SDT checkbox, `placeholderDocPart` prompt text, grouped SDTs, custom richtext styling), build the skeleton once in Word, then fill via CLI.\n\n**One-time in Word:** File → Options → Customise Ribbon → Developer. Developer tab → Insert Picture / Check Box / Grouping Content Control → right-click → Properties → set Title (`alias`) + Tag. Save as `template.docx`.\n\n**Fill via CLI:**\n\n```bash\ncp templates/onboarding_with_signature.docx \"$FILE\"\nofficecli open \"$FILE\"\nofficecli view \"$FILE\" forms                                    # see /body/... paths + sdtId values\nofficecli set \"$FILE\" '/body/sdt[@sdtId=3]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\n```\n\n## MERGEFIELD (data-driven track)\n\n`help docx field` declares a `fieldType` enum of ~30 values including `mergefield`, `ref`, `pageref`, `seq`, `if` — all CLI-expressible with their typed props. MERGEFIELD coexists with SDT in the same file but is reported by `query field` only; `view forms` does NOT list MERGEFIELDs (they are not user-fillable).\n\n**Canonical MERGEFIELD:**\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Dear \"\nofficecli add \"$FILE\" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CustomerName\nofficecli add \"$FILE\" '/body/p[1]' --type run --prop text=\", \"\nofficecli add \"$FILE\" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CompanyName\n# Readback: \"Dear «CustomerName», «CompanyName»\"\n```\n\n**Element-type shortcut** (equivalent): `officecli add \"$FILE\" '/body/p[1]' --type mergefield --prop name=CustomerName`.\n\n### Common field patterns\n\n| Pattern | Call shape |\n|---|---|\n| Mail-merge placeholder | `--type field --prop fieldType=mergefield --prop name=<FieldName>` |\n| Mail-merge with numeric picture (money, percent) | `--type field --prop fieldType=mergefield --prop name=Amount --prop instr='MERGEFIELD Amount \\# \"#,##0.00\"'`. The typed `format` prop is ignored for mergefield (prints a warning) — use `instr` (alias `instruction`) to embed the full field code. Verify: `query \"$FILE\" field --json \\| jq '.data.results[].format.instruction'` must contain `\\#` and the picture. |\n| Mail-merge with date picture | `--type field --prop fieldType=mergefield --prop name=StartDate --prop instr='MERGEFIELD StartDate \\@ \"yyyy-MM-dd\"'` |\n| Cross-reference to bookmark text | `--type field --prop fieldType=ref --prop name=<BookmarkName>` |\n| Cross-reference to bookmark's page number | `--type field --prop fieldType=pageref --prop name=<BookmarkName>` |\n| Auto-numbering (Figure 1 / 2 / 3) | `--type field --prop fieldType=seq --prop identifier=Figure` |\n| Page number in footer | `--type field --prop fieldType=page` |\n| \"Page X of Y\" | two fields: `fieldType=page` + `fieldType=numpages` |\n| Conditional text | `--type field --prop fieldType=if --prop expression='{ MERGEFIELD Gender } = \"Male\"' --prop trueText=\"Mr.\" --prop falseText=\"Ms.\"` |\n\n### IF conditional (CLI-expressible)\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"\"\nofficecli add \"$FILE\" '/body/p[last()]' --type field --prop fieldType=if \\\n  --prop expression='{ MERGEFIELD Gender } = \"Male\"' \\\n  --prop trueText=\"Mr.\" --prop falseText=\"Ms.\"\nofficecli add \"$FILE\" '/body/p[last()]' --type run --prop text=\" \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field --prop fieldType=mergefield --prop name=LastName\n# Merge-time result: \"Mr. «LastName»\" or \"Ms. «LastName»\"\n```\n\nNested wrappers like `{ IF { MERGEFIELD X } = \"Y\" { REF bm } \"fallback\" }` are not expressible via `--prop` chaining — drop to raw-set a hand-crafted `<w:fldChar>` / `<w:instrText>` fragment, or build once in a Word template (Path C).\n\n**Readback.** `query $FILE field` lists `/field[N]` + instruction + `fieldType`. `view $FILE forms` does NOT list MERGEFIELDs (only SDT + formfield) — they are template-time, not end-user fillable. `get $FILE '/body/p[1]'` renders the guillemet-wrapped field name.\n\n## Legacy FormField\n\nUse FormField **when you need a real checkbox**. For text/dropdown, prefer SDT.\n\n`help docx formfield`: `type` (text/checkbox/check/dropdown), `name` (required, **≤ 20 chars** — OpenXML schema MaxLength; add passes longer but `validate` rejects), `text` (text only, alias `value`), `checked` (checkbox only).\n\n```bash\n# CHECKBOX — the only real checkbox (SDT type=checkbox is not implemented)\nofficecli add \"$FILE\" /body --type formfield --prop type=checkbox \\\n  --prop name=agree_terms --prop checked=false\n\n# TEXT formfield\nofficecli add \"$FILE\" /body --type formfield --prop type=text \\\n  --prop name=emp_name --prop text=\"Enter name\"\n\n# DROPDOWN formfield — items NOT settable via CLI; use Word template or SDT Path B\nofficecli add \"$FILE\" /body --type formfield --prop type=dropdown --prop name=dept_select\n\n# Read / modify by name (stable) or 1-based index\nofficecli get \"$FILE\" '/formfield[agree_terms]'\nofficecli set \"$FILE\" '/formfield[agree_terms]' --prop checked=true\nofficecli set \"$FILE\" '/formfield[emp_name]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" '/formfield[dept_select]' --prop text=\"Engineering\"\n```\n\nFormField paths (`/formfield[N]` or `/formfield[<name>]`) are separate from SDT paths (`/body/sdt[N]`). Both coexist; `protection=forms` covers both.\n\n**Scale.** Tested with 50+ checkboxes in a single document — no practical cap on formfield count; build and `validate` remain clean. `name` ≤ 20 chars (K13) is the only hard constraint.\n\n**Renderer note — formfield checkbox `[RENDERER-BUG]`.** LibreOffice's PDF export occasionally renders the formfield checkbox as `☐☐` (doubled box). Word and WPS render a single clickable box (toggles ☑). This is a LibreOffice renderer quirk, **not a skill or document quality issue** — see K19. Do not attempt workarounds in the form; if an evaluator screenshots a LibreOffice-generated PDF and sees `☐☐`, attribute to `[RENDERER-BUG]`.\n\n## Document protection & lock\n\n### Enabling form protection\n\n```bash\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli get \"$FILE\" /                                  # look for: protectionEnforced=True\n```\n\n### Protection modes\n\n| Mode | Word user can | CLI behavior |\n|---|---|---|\n| `forms` | Fill SDT + formfield only | Form-field (SDT) edits work (exit 0); non-field content edits need `--force` or `raw-set` |\n| `readOnly` | Read only | Non-field edits need `--force`; raw-set bypasses |\n| `comments` | Add comments only | Non-field edits need `--force`; raw-set bypasses |\n| `trackedChanges` | Edit with tracked changes only | Non-field edits need `--force`; raw-set bypasses |\n| `none` | Full editing | All ops work |\n\n**KEY:** Document protection restricts Word users AND the CLI. Under `protection=forms`, form-field (SDT) edits — `set /body/sdt[N] --prop text=` / `--prop alias=` — succeed (exit 0); this is the point of forms protection. Only NON-field content edits (e.g. `set /body/p[N] --prop text=`) are refused with `ERROR: Document is protected (mode: forms). … use --force to override`; pass `--force` (or `raw-set`, the only verb that fully bypasses protection) to edit static content anyway. Use `Query(\"editable\")` to find the fields a Word user could still fill.\n\n### Lock values (settable on `add` and `set`)\n\n```bash\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked           # content editable; control cannot be deleted\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=contentlocked       # content read-only; control can be deleted\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtcontentlocked    # both locked\n# Omit lock entirely → unlocked (default)\n```\n\n`--prop lock=...` works on `add` as well as `set` — set it inline when you create the control. Readback normalises to camelCase (`sdtLocked`) regardless of input case — both accepted.\n\n### lock × `protection=forms` interaction\n\n| lock value | `protection=forms` active | Word user can edit? | Word user can delete control? |\n|---|---|---|---|\n| (none) | yes | **Yes** | **Yes** |\n| `sdtlocked` | yes | Yes | No |\n| `contentlocked` | yes | No | Yes |\n| `sdtcontentlocked` | yes | No | No |\n| block-level SDT wrap `contentlocked` | any | No (wrapped paragraph read-only regardless of protection) | No |\n| any | `readOnly` mode | No | No |\n\n### Block-level lock (paragraph-wrapping SDT)\n\n`protection=forms` is document-level — once an admin unprotects, every static paragraph (disclaimer, legal attestation, contract clause) becomes editable again. Master templates need defense-in-depth: wrap the critical paragraph in a block-level `<w:sdt>` with `lock=contentLocked`, so the content stays read-only even after protection is stripped.\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"I authorize the above and acknowledge all clauses.\" --prop size=11 --prop spaceAfter=12pt\nPID=$(officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId')\n\n# raw-set actions: append | prepend | insertbefore | insertafter | replace | remove | setattr\n# No `wrap` action — two-step instead: (1) insertbefore an empty <w:sdt><w:sdtContent/></w:sdt>,\n# (2) move the original <w:p> inside by `replace` on the sdtContent with a copy of the paragraph XML.\n# Simpler alternative: read the paragraph XML via `officecli raw`, then `replace` the whole <w:p> with <w:sdt>...<w:sdtContent>[original w:p]</w:sdtContent></w:sdt>:\n# NOTE: this needs an XML-aware extraction, NOT awk. `raw /document` emits the whole document on ONE line,\n# so an awk range like `/paraId=.../,/<\\/w:p>/` grabs the entire <w:document> (schema-invalid). Parse the XML\n# and pull the single <w:p> by paraId instead:\nPARA_XML=$(officecli raw \"$FILE\" /document | python3 -c '\nimport sys, xml.etree.ElementTree as ET\nxml = sys.stdin.read(); pid = sys.argv[1]\nns = {\"w\":\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\"w14\":\"http://schemas.microsoft.com/office/word/2010/wordml\"}\nfor p, u in ns.items(): ET.register_namespace(p, u)\nroot = ET.fromstring(xml); key = \"{%s}paraId\" % ns[\"w14\"]\nfor p in root.iter(\"{%s}p\" % ns[\"w\"]):\n    if p.attrib.get(key) == pid:\n        sys.stdout.write(ET.tostring(p, encoding=\"unicode\")); break\n' \"$PID\")\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:p[@w14:paraId='$PID']\" \\\n  --action replace \\\n  --xml \"<w:sdt xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" xmlns:w14=\\\"http://schemas.microsoft.com/office/word/2010/wordml\\\"><w:sdtPr><w:alias w:val=\\\"Authorization\\\"/><w:tag w:val=\\\"auth_para\\\"/><w:lock w:val=\\\"contentLocked\\\"/></w:sdtPr><w:sdtContent>${PARA_XML}</w:sdtContent></w:sdt>\"\n```\n\nVerify with `query sdt --json | jq '.data.results[] | select(.format.lock == \"contentLocked\" and .format.type == \"block\")'`. Use only for legal attestations, compliance disclaimers, confidentiality clauses — regular intake fields do not need this.\n\n### Role-gated fields (multi-role forms)\n\nWhen one form is filled by two roles (patient vs physician; Party A vs Party B), use `lock=contentLocked` on the fields the other role must not touch. Under `protection=forms`, `contentLocked` SDTs display as read-only in Word; the intended role unprotects (or the admin swaps role-specific copies) to fill the other half.\n\n```bash\n# Patient section — editable (no lock, or sdtlocked to prevent accidental deletion only)\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked      # patient_name\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked      # patient_dob\n\n# Physician section — locked against patient edits\nofficecli set \"$FILE\" '/body/sdt[14]' --prop lock=contentLocked # physician_diagnosis\nofficecli set \"$FILE\" '/body/sdt[15]' --prop lock=contentLocked # physician_signature\n```\n\nThis is the core pattern for medical intake, two-party contracts, sequential-approval forms.\n\n## Recipe — Contract / SOW template with MERGEFIELD + signature\n\nRow-map across the three sub-recipes: SDT[1]=project_name, SDT[2]=contract_start, SDT[3]=payment_schedule, SDT[4]=signatory_name (inline). Run (sow-a) → (sow-b) → (sow-c) in order on the same `$FILE`; each sub-recipe stays under 20 lines so a shell-escape slip never cascades past one block.\n\n### Recipe (sow-a) Boilerplate + cover + parties\n\nCreates the file, sets docDefaults, writes the title / intro, and drops the two MERGEFIELD placeholders (`CustomerName`, `ContractNo`) that downstream mail-merge will fill.\n\n```bash\nFILE=sow.docx\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" / --prop title=\"Statement of Work\" \\\n  --prop docDefaults.font=\"Calibri\" --prop docDefaults.fontSize=\"12pt\"\n\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Statement of Work\" \\\n  --prop style=Heading1 --prop size=20 --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"This Statement of Work ('SOW') is entered into between the parties identified below and governs the delivery of professional services.\" \\\n  --prop size=11 --prop spaceAfter=12pt\n\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Customer: \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field \\\n  --prop fieldType=mergefield --prop name=CustomerName\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Contract #: \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field \\\n  --prop fieldType=mergefield --prop name=ContractNo\n```\n\n### Recipe (sow-b) SDT fields — all via Path A\n\nAdds the three block-level SDTs (project / date / dropdown) and the inline signature SDT anchored via `--after 'find:Client Signature:'`. The date format and dropdown items go straight on `add` — no raw-set needed.\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Project Name\" --prop tag=project_name --prop text=\"Enter project name\"\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Contract Start Date\" --prop tag=contract_start --prop format=\"MM/dd/yyyy\"\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown \\\n  --prop alias=\"Payment Schedule\" --prop tag=payment_schedule \\\n  --prop items=\"Full Prepayment,Net 30 Upon Delivery\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Client Signature:\" \\\n  --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Signatory Name\" --prop tag=signatory_name --prop text=\"Authorized Signatory\" \\\n  --after 'find:Client Signature:'\n# (Only reach for raw-set if a listItem's stored value must differ from its display text — see Path B.)\n```\n\n### Recipe (sow-c) Watermark + locks + document protection\n\nDrops the CONFIDENTIAL watermark (parent is `/`, never `/body`), locks the three block-level SDTs, instructs how to lock the inline signatory_name SDT (path only known after `view forms`), then seals the document with `protection=forms` as the last command.\n\n```bash\nofficecli add \"$FILE\" / --type watermark \\\n  --prop text=\"CONFIDENTIAL\" --prop color=FF0000 --prop rotation=315\n\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[3]' --prop lock=sdtlocked\nofficecli view \"$FILE\" forms   # copy signatory_name path, then: set '/body/p[@paraId=...]/sdt[1]' --prop lock=sdtlocked\n\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\nofficecli query \"$FILE\" field     # expect 2 MERGEFIELDs: CustomerName, ContractNo\n```\n\n## Design principles (forms)\n\n**Control-type decision tree:**\n\n```\nDate → type=date | Fixed list → type=dropdown | List + custom → type=combobox\nShort text → type=text | Long text → type=richtext | Boolean → formfield checkbox\n```\n\n**Typography scale.** Spacing unit trap: `spaceBefore` / `spaceAfter` / `spaceLine` default to **twips** (1/20 pt) — always write `spaceBefore=18pt`.\n\n| Element | Size | Style | Spacing |\n|---|---|---|---|\n| Form title (H1) | 20pt | Bold | `spaceBefore=0pt`, `spaceAfter=12pt` |\n| Section heading (H2) | 14pt | Bold | `spaceBefore=18pt`, `spaceAfter=8pt` |\n| Field label | 11pt | Bold | `spaceAfter=4pt` |\n| Instructions / notes | 11pt | Italic `color=666666` | `spaceAfter=18pt` |\n\n**Accessibility bump.** For medical / geriatric / accessibility-focused forms, raise field label + instruction to **12pt** (11pt default is tight for older users); keep section headings at 14pt.\n\n**CJK forms:** set `docDefaults.font=\"Microsoft YaHei\"` — Calibri lacks Chinese glyphs.\n\n**Field ordering.** (1) Personal / ID, (2) role / classification, (3) dates, (4) supplemental free-text, (5) confirmation / signature.\n\n**Yes/No + conditional follow-up** (common in compliance / medical intake): formfield checkbox followed by a richtext SDT whose `alias` carries the cue — e.g. `--type formfield --prop type=checkbox --prop name=has_cond` then `--type sdt --prop type=richtext --prop alias=\"If yes, explain\" --prop tag=cond_detail --prop text=\"If yes, explain here\"`.\n\n**Signature block order.** Label on its own paragraph, SDT on the next paragraph (with `spaceBefore=18pt` on the label, `spaceAfter=4pt` on the SDT). Never `Label: SDT` inline — Word renders the runs as touching, visually stuck together.\n\n**Build order.** create+open → metadata → structure (headings, label paragraphs) → SDT/formfield skeletons (Path A 4 props) → Path B injections → per-field lock → `protection=forms` LAST → close.\n\n**Header / footer note.** Headers/footers are **predefined** when the section is created (default/first/even, 3 each). The first mutation must be `set` against the existing part, not `add` — `add $FILE /header ...` returns `already exists` or silently no-ops. Inspect first with `officecli query \"$FILE\" header --json` to read the `type` values, then `officecli set \"$FILE\" '/header[@type=default]' --prop text=...`. Only use `add` when creating an additional section with its own header/footer.\n\n## Batch mode (brief)\n\nFor forms with many controls, batch reduces overhead. Path A + Path B coexist in one batch.\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/body\",\"type\":\"sdt\",\"props\":{\"type\":\"text\",\"alias\":\"Full Name\",\"tag\":\"full_name\",\"text\":\"Enter name\"}},\n  {\"command\":\"add\",\"parent\":\"/body\",\"type\":\"sdt\",\"props\":{\"type\":\"dropdown\",\"alias\":\"Department\",\"tag\":\"dept\"}},\n  {\"command\":\"raw-set\",\"part\":\"/document\",\"xpath\":\"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\",\"action\":\"append\",\"xml\":\"<w:listItem xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" w:displayText=\\\"Engineering\\\" w:value=\\\"Engineering\\\"/><w:listItem xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" w:displayText=\\\"Finance\\\" w:value=\\\"Finance\\\"/>\"},\n  {\"command\":\"set\",\"path\":\"/body/sdt[1]\",\"props\":{\"lock\":\"sdtlocked\"}},\n  {\"command\":\"set\",\"path\":\"/body/sdt[2]\",\"props\":{\"lock\":\"sdtlocked\"}}\n]\nEOF\nofficecli set \"$FILE\" / --prop protection=forms\n```\n\n- Escape inner `\"` in `xml` with `\\\"`. Use single-quoted heredoc `<<'EOF'` so `$var` does not expand.\n- **P0 batch trap:** genuinely-unsupported props in batch are silently dropped, **no WARNING** (interactive `add` would print WARNING: UNSUPPORTED, exit 2). Defence: send only props `help docx sdt` lists (`type/tag/alias/text/items/format/lock/...` are fine on `add`; `maxlength` is not), and verify with a readback after the batch.\n- `batch` supports `add`, `set`, `get`, `query`, `remove`, `validate`, `raw-set`.\n\n## Delivery Gate (executable)\n\nRun every gate below after every form. Each gate must print its `OK` line. Any `REJECT` = do not deliver.\n\n```bash\n# Assumes FILE=<your-form.docx>, document has been closed with officecli close \"$FILE\"\n\n# Gate 1 — Validate (must be clean; protection=forms no longer produces a schema error)\nVAL_OUT=$(officecli validate \"$FILE\" 2>&1)\nVAL_ERRS=$(echo \"$VAL_OUT\" | grep -c '\\[Schema\\]')\nif [ \"$VAL_ERRS\" -eq 0 ]; then echo \"Gate 1 OK (validate clean)\"\nelse echo \"REJECT Gate 1: $VAL_ERRS schema errors\"; echo \"$VAL_OUT\"; exit 1\nfi\n\n# Gate 2 — Token / placeholder leak (labels used as visual underscore substitutes)\nLEAK=$(officecli view \"$FILE\" text | grep -niE '_{3,}|TBD|\\(fill in\\)|\\{\\{|xxxx|lorem|placeholder')\n[ -z \"$LEAK\" ] && echo \"Gate 2 OK (no underscore / placeholder leak)\" || { echo \"REJECT Gate 2:\"; echo \"$LEAK\"; exit 1; }\n\n# Gate 3 — At least one structured field exists\nSDT_N=$(officecli query \"$FILE\" sdt --json | jq '.data.results | length')\nFF_N=$(officecli query \"$FILE\" formfield --json | jq '.data.results | length')\nFLD_N=$(officecli query \"$FILE\" field --json | jq '.data.results | length')\nTOTAL=$((SDT_N + FF_N + FLD_N))\n[ \"$TOTAL\" -gt 0 ] && echo \"Gate 3 OK ($SDT_N sdt + $FF_N formfield + $FLD_N field)\" || { echo \"REJECT Gate 3: 0 structured fields — this is not a form\"; exit 1; }\n\n# Gate 4 — Every SDT has alias + tag (skill-imposed H2)\n# NOTE: `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` — use `.data.results[0].format.alias` / `.format.tag`, never bare `.alias` or `.data.format`.\nSDT_MISSING=$(officecli query \"$FILE\" sdt --json | jq '[.data.results[] | select(.format.alias == null or .format.alias == \"\" or .format.tag == null or .format.tag == \"\")] | length')\n[ \"$SDT_MISSING\" -eq 0 ] && echo \"Gate 4 OK (every SDT has alias+tag)\" || { echo \"REJECT Gate 4: $SDT_MISSING SDT(s) missing alias or tag\"; exit 1; }\n\n# Gate 5 — Protection enforced + per-field lock inventory\nPROT=$(officecli get \"$FILE\" / --json | jq -r '.data.results[0].format.protection // \"none\"')\n[ \"$PROT\" = \"forms\" ] && echo \"Gate 5 OK (protection=forms enforced)\" || { echo \"REJECT Gate 5: protection is '$PROT', expected 'forms'\"; exit 1; }\nofficecli view \"$FILE\" forms | head -40   # visual spot-check: every dropdown shows items=; every date shows format=; every locked SDT shows lock=\n\n# Gate 6 — No type=checkbox leaked onto any SDT\nBAD_CB=$(officecli query \"$FILE\" sdt --json | jq '[.data.results[] | select(.format.type == \"checkbox\")] | length')\n[ \"$BAD_CB\" -eq 0 ] && echo \"Gate 6 OK (no SDT checkbox — formfield only)\" || { echo \"REJECT Gate 6: $BAD_CB SDT with type=checkbox\"; exit 1; }\n```\n\n**Why `view issues` is not a gate.** It runs only prose-style checks (first-line-indent, heading size) and flags every form label as `Body paragraph missing first-line indent` — a false-positive avalanche on forms. Ignore for this skill. Use `validate` (schema integrity) and `view forms` (field inventory).\n\n## Known Issues\n\n| # | Issue | Behavior | Workaround |\n|---|---|---|---|\n| K1 | SDT `type=checkbox` not implemented | `add ... --type sdt --prop type=checkbox` → `Error: SDT type 'checkbox' is not implemented`, exit 1 (error now lists the supported types incl. group/picture) | Use `--type formfield --prop type=checkbox` |\n| K3 | SDT `maxlength` UNSUPPORTED on `add` | `WARNING: UNSUPPORTED: maxlength`, exit 2; element still created. (`items` / `format` / `lock` / `placeholderText` are NOW supported on `add` — only `maxlength` is rejected; `name` is accepted but a no-op on SDT — use `alias`/`tag`) | Enforce length downstream; use `text` for initial content |\n| K4 | SDT `items` / `format` / `type` not settable AFTER creation | `set --prop items=...` → `UNSUPPORTED props (use raw-set instead)`. (They ARE settable on `add` — set them inline at creation) | Set at `add` time; to change later, Path B `raw-set` or `remove` + re-add |\n| K5 | FormField `maxlength` UNSUPPORTED | `WARNING: UNSUPPORTED: maxlength`; formfield created | Enforce length in downstream validation |\n| K6 | FormField dropdown `items` UNSUPPORTED | Dropdown formfield is created with empty option list | Use an SDT dropdown with `--prop items=` instead |\n| K7 | Watermark `width` / `height` not settable | Watermark created without them; `opacity` IS settable on `add` and reads back (real dims are computed) | For an exact size, open Word + adjust the shape (Phase 2) |\n| K9 | Batch mode silently drops genuinely-UNSUPPORTED props | No `WARNING` line; batch reports \"N succeeded\" even when a prop (e.g. `maxlength`) was dropped | Keep batch SDT entries to props `help docx sdt` lists; verify with a readback after the batch |\n| K13 | FormField `name` > 20 characters | `add` returns exit 0 with no warning; `validate` later reports `[Schema] ... MaxLength=20` on `/w:ffData/w:name` | Keep `name` ≤ 20 characters (OpenXML schema limit). SDT `alias` / `tag` have no such limit |\n| K14 | `shd.fill` on a paragraph emits schema-invalid `<w:pPr>/<w:shd>` | `validate` reports 2 schema errors per instance (`unexpected child element`, `required attribute 'val' missing`); Word renders it anyway | Apply highlight on the run instead (`shading=HEX`, flat canonical), or raw-set `<w:shd w:val=\"clear\" w:fill=\"HEX\"/>` into the run's `<w:rPr>` |\n| K15 | `view forms` does NOT list MERGEFIELDs | Only SDT + formfield in output; MERGEFIELDs are template-time, not end-user fillable | Treat `query field` and `view forms` as two disjoint inventories. Every recipe verifies both |\n| K16 | Header / footer are predefined at section creation (default/first/even, 3 each) | `add $FILE /header ...` returns `already exists` or silently no-ops on the first call | First mutation uses `set` against the existing part: `officecli query $FILE header --json` to read `type`, then `set '/header[@type=default]' --prop text=...`. Only use `add` for a brand-new section's header/footer |\n| K17 | Watermark injected into header emits `<w:noProof>` child that is schema-invalid | `validate` adds an extra `[Schema]` error at `/header[N]/w:sdt/.../w:noProof` — a real schema error to fix, not a benign one | After `add $FILE / --type watermark`, run once per header part: `officecli raw-set $FILE /word/header1.xml --xpath \"//w:noProof\" --action remove` (repeat for `header2.xml`, `header3.xml` if present) |\n| K18 | `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` | Writing jq against bare `.alias` / `.tag` / `.protection`, or `.data.format.*`, returns null/0 matches and Gate 4/5 falsely report \"missing=N\" | Use `.data.results[].format.alias` / `.format.tag`; for `get /` use `.data.results[0].format.protection` (NOT `.data.format.protection`). Same for `.format.type` / `.format.paraId` |\n| K19 | LibreOffice renders formfield checkbox as `☐☐` (double box) in PDF export | Cosmetic only — Word / WPS render a single box, clickable to toggle ☑. A LibreOffice renderer quirk, flagged as [RENDERER-BUG] | Do not try to \"fix\" in the skill. If an evaluator screenshots from LibreOffice-generated PDF and sees `☐☐`, attribute to [RENDERER-BUG], not a form-quality defect |\n\n## Phase 2 — enhance in Word\n\nSome polish is out of CLI scope. Hand the file to a human for these; none are required for a valid form.\n\n| Need | Why open Word |\n|---|---|\n| Real SDT checkbox with specific locking | `type=checkbox` exits 1; use Developer → Check Box Content Control (or a legacy FormField checkbox via CLI) |\n| Prompt text (\"Click here to enter a date\") | Needs `placeholderDocPart` in `/word/glossary/document.xml` |\n| Custom richtext default appearance | Adjust the referenced style in Word's style pane |\n| Watermark resize | `width` / `height` not settable; drag shape handles |\n\n(`picture` and `group` SDTs add directly via `--type sdt --prop type=picture` / `type=group` — no longer Phase-2 items.)\n\nFor the first four, build the skeleton once (Path C) and reuse.\n\n## Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <element> --json`. Help is the authoritative schema; this skill is the decision guide for building real fillable Word forms on top of it.\n","SKILL.md":"---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n","skills/officecli/SKILL.md":"---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n","plugins/plugin-protocol.md":"# Plugins Skill\n\nPath: `plugins/plugin-protocol.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/plugins/plugin-protocol.md)","skills/morph-ppt/reference/decision-rules.md":"# Reference Skill\n\nPath: `skills/morph-ppt/reference/decision-rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/decision-rules.md)","skills/morph-ppt/reference/pptx-design.md":"# Reference Skill\n\nPath: `skills/morph-ppt/reference/pptx-design.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/pptx-design.md)","skills/morph-ppt/reference/styles/INDEX.md":"# Styles Skill\n\nPath: `skills/morph-ppt/reference/styles/INDEX.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/INDEX.md)","skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md":"# Bw--brutalist-raw Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md)","skills/morph-ppt/reference/styles/bw--mono-line/style.md":"# Bw--mono-line Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--mono-line/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--mono-line/style.md)","skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md":"# Bw--swiss-bauhaus Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md)","skills/morph-ppt/reference/styles/bw--swiss-system/style.md":"# Bw--swiss-system Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-system/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-system/style.md)","skills/morph-ppt/reference/styles/dark--architectural-plan/style.md":"# Dark--architectural-plan Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--architectural-plan/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--architectural-plan/style.md)","skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md":"# Dark--aurora-softedge Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md)","skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md":"# Dark--blueprint-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md)","skills/morph-ppt/reference/styles/dark--circle-digital/style.md":"# Dark--circle-digital Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--circle-digital/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--circle-digital/style.md)","skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md":"# Dark--cosmic-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md)","skills/morph-ppt/reference/styles/dark--cyber-future/style.md":"# Dark--cyber-future Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cyber-future/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cyber-future/style.md)","skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md":"# Dark--diagonal-cut Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md)","skills/morph-ppt/reference/styles/dark--editorial-story/style.md":"# Dark--editorial-story Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--editorial-story/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--editorial-story/style.md)","skills/morph-ppt/reference/styles/dark--investor-pitch/style.md":"# Dark--investor-pitch Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--investor-pitch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--investor-pitch/style.md)","skills/morph-ppt/reference/styles/dark--liquid-flow/style.md":"# Dark--liquid-flow Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--liquid-flow/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--liquid-flow/style.md)","skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md":"# Dark--luxury-minimal Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md)","skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md":"# Dark--midnight-blueprint Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md)","skills/morph-ppt/reference/styles/dark--neon-productivity/style.md":"# Dark--neon-productivity Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--neon-productivity/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--neon-productivity/style.md)","skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md":"# Dark--obsidian-amber Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md)","skills/morph-ppt/reference/styles/dark--premium-navy/style.md":"# Dark--premium-navy Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--premium-navy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--premium-navy/style.md)","skills/morph-ppt/reference/styles/dark--sage-grain/style.md":"# Dark--sage-grain Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--sage-grain/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--sage-grain/style.md)","skills/morph-ppt/reference/styles/dark--space-odyssey/style.md":"# Dark--space-odyssey Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--space-odyssey/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--space-odyssey/style.md)","skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md":"# Dark--spotlight-stage Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md)","skills/morph-ppt/reference/styles/dark--velvet-rose/style.md":"# Dark--velvet-rose Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--velvet-rose/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--velvet-rose/style.md)","skills/morph-ppt/reference/styles/light--bold-type/style.md":"# Light--bold-type Skill\n\nPath: `skills/morph-ppt/reference/styles/light--bold-type/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--bold-type/style.md)","skills/morph-ppt/reference/styles/light--firmwise-saas/style.md":"# Light--firmwise-saas Skill\n\nPath: `skills/morph-ppt/reference/styles/light--firmwise-saas/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--firmwise-saas/style.md)","skills/morph-ppt/reference/styles/light--fluid-gradient/style.md":"# Light--fluid-gradient Skill\n\nPath: `skills/morph-ppt/reference/styles/light--fluid-gradient/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--fluid-gradient/style.md)","skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md":"# Light--glassmorphism-vc Skill\n\nPath: `skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md)","skills/morph-ppt/reference/styles/light--isometric-clean/style.md":"# Light--isometric-clean Skill\n\nPath: `skills/morph-ppt/reference/styles/light--isometric-clean/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--isometric-clean/style.md)","skills/morph-ppt/reference/styles/light--minimal-corporate/style.md":"# Light--minimal-corporate Skill\n\nPath: `skills/morph-ppt/reference/styles/light--minimal-corporate/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-corporate/style.md)","skills/morph-ppt/reference/styles/light--minimal-product/style.md":"# Light--minimal-product Skill\n\nPath: `skills/morph-ppt/reference/styles/light--minimal-product/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-product/style.md)","skills/morph-ppt/reference/styles/light--project-proposal/style.md":"# Light--project-proposal Skill\n\nPath: `skills/morph-ppt/reference/styles/light--project-proposal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--project-proposal/style.md)","skills/morph-ppt/reference/styles/light--spring-launch/style.md":"# Light--spring-launch Skill\n\nPath: `skills/morph-ppt/reference/styles/light--spring-launch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--spring-launch/style.md)","skills/morph-ppt/reference/styles/light--training-interactive/style.md":"# Light--training-interactive Skill\n\nPath: `skills/morph-ppt/reference/styles/light--training-interactive/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--training-interactive/style.md)","skills/morph-ppt/reference/styles/light--watercolor-wash/style.md":"# Light--watercolor-wash Skill\n\nPath: `skills/morph-ppt/reference/styles/light--watercolor-wash/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--watercolor-wash/style.md)","skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md":"# Mixed--bauhaus-blocks Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md)","skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md":"# Mixed--chromatic-aberration Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md)","skills/morph-ppt/reference/styles/mixed--duotone-split/style.md":"# Mixed--duotone-split Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--duotone-split/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--duotone-split/style.md)","skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md":"# Mixed--spectral-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md)","skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md":"# Vivid--bauhaus-electric Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md)","skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md":"# Vivid--candy-stripe Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md)","skills/morph-ppt/reference/styles/vivid--energy-neon/style.md":"# Vivid--energy-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--energy-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--energy-neon/style.md)","skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md":"# Vivid--pink-editorial Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md)","skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md":"# Vivid--playful-marketing Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md)","skills/morph-ppt/reference/styles/warm--bloom-academy/style.md":"# Warm--bloom-academy Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--bloom-academy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--bloom-academy/style.md)","skills/morph-ppt/reference/styles/warm--brand-refresh/style.md":"# Warm--brand-refresh Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--brand-refresh/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--brand-refresh/style.md)","skills/morph-ppt/reference/styles/warm--coral-culture/style.md":"# Warm--coral-culture Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--coral-culture/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--coral-culture/style.md)","skills/morph-ppt/reference/styles/warm--earth-organic/style.md":"# Warm--earth-organic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--earth-organic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--earth-organic/style.md)","skills/morph-ppt/reference/styles/warm--monument-editorial/style.md":"# Warm--monument-editorial Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--monument-editorial/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--monument-editorial/style.md)","skills/morph-ppt/reference/styles/warm--playful-organic/style.md":"# Warm--playful-organic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--playful-organic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--playful-organic/style.md)","skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md":"# Warm--sunset-mosaic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md)","skills/morph-ppt/reference/styles/warm--vital-bloom/style.md":"# Warm--vital-bloom Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--vital-bloom/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--vital-bloom/style.md)"},"files":{"skills/officecli-data-dashboard/SKILL.md":"---\nname: officecli-data-dashboard\ndescription: \"Use this skill to build a multi-element Excel dashboard — Dashboard sheet on open, multiple formula-driven KPI cards, multiple charts, sparklines, and conditional formatting — from CSV or tabular input. Trigger on: 'dashboard', 'KPI dashboard', 'analytics dashboard', 'executive dashboard', 'metrics dashboard', 'CSV to dashboard', 'data visualization'. Output is a single .xlsx. Scene-layer on officecli-xlsx: inherits every xlsx hard rule. DO NOT invoke for: a single budget tracker / one-sheet CSV-with-formatting (use xlsx), a 3-statement / DCF / LBO financial model (use financial-model), a weekly report with at most 1 chart and fewer than 10 rows (use xlsx).\"\n---\n\n# Data Dashboard (scene-layer on officecli-xlsx)\n\nA dashboard is not \"a spreadsheet with charts\". It is a composition: **one Dashboard sheet the user lands on** with formula-driven KPI cards, cell-range-linked charts, sparklines, and semantic conditional formatting. Everything else (raw data, aggregations) is upstream infrastructure the user should never need to open. This skill teaches the composition pattern. Everything about the xlsx engine — cells, formulas, batch JSON, shell quoting, validate, HTML preview — comes from `officecli-xlsx` and is not re-taught here.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**When a prop name, enum value, or alias is uncertain, consult help before guessing.**\n\n```bash\nofficecli help xlsx                          # element list\nofficecli help xlsx chart                    # full schema for charts\nofficecli help xlsx sparkline                # sparklines\nofficecli help xlsx conditionalformatting    # all CF rule types\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help wins**. DeferredAddKeys (`combosplit`, `holesize`) work on `add` only — see Reference.\n\n## Mental Model & Inheritance\n\nThis skill **inherits every xlsx hard rule** from `officecli-xlsx` — shell quoting, zero formula errors, visual delivery floor, batch JSON shape (`{\"command\":\"set\"|\"add\",\"path\":...,\"props\":{...}}` — key is `command`, NOT `action`), batch JSON dotted-name rule, chart data-feed forms, batch+resident limits, `validate` discipline. Read officecli-xlsx first; honour those rules, do not re-teach them here.\n\n**Reverse handoff — do NOT use this skill when:**\n\n- The ask is a **single-sheet CSV-with-formatting tracker** (no Dashboard sheet, no KPI cards, ≤ 1 chart) → go back to `officecli-xlsx`.\n- The ask is a **3-statement / DCF / LBO financial model** with blue-inputs / black-formulas / cross-sheet drivers → use `officecli-financial-model`.\n- The ask is a **weekly status report** with one SUMIF summary and one chart over < 10 rows → `officecli-xlsx`.\n\nThis skill only accepts: \"a Dashboard sheet the user opens first, multiple KPI cards, multiple charts, some CF / sparklines\".\n\n## Shell & Execution Discipline\n\n→ see officecli-xlsx §Shell & Execution Discipline for the baseline (quoting, heredoc for `!`, incremental execution).\n\nTwo increments specific to dashboards:\n\n- **Long chart `add` commands exceed 180 chars.** Always split across lines with trailing `\\`; never pack a chart command onto a single line. The longer the command, the higher the chance a shell-escape bug hides inside it.\n- **Multi-instance counts use `query --json | jq length`, never `raw-get | grep -c`.** Example: `officecli query \"$FILE\" chart --json | jq '.data.results | length'` for \"how many charts do I have?\".\n\n## Core Principles\n\nFive non-negotiable principles. If any one is violated the output is not a dashboard, it is a spreadsheet that happens to have a chart.\n\n1. **Formula-driven KPIs.** Every KPI value on the Dashboard sheet is a formula — `SUM`, `AVERAGE`, `IFERROR((...-...)/...,0)`, whatever — referring to cells on the Data / Summary sheet. Never hardcode a computed number. When the underlying data changes tomorrow, KPIs update on open.\n\n2. **Cell-range references for charts.** Every chart series reads from a cell range: `series1.values=\"Sheet1!B2:B13\"`. Inline `data=\"Revenue:100,200,300\"` is for a 5-minute demo, not a delivered dashboard. The one exception: data requires an aggregation Excel cannot express (rare) — document the exception in a comment cell.\n\n3. **Dashboard-first architecture.** KPI label cells, KPI value cells, charts, sparklines all live on the **Dashboard** sheet — the single sheet a user lands on. Raw imports and `SUMIFS` rollups live on Data / Summary sheets, upstream of the Dashboard. The user should never need to switch tabs to find the answer.\n\n4. **Visible cells only for chart sources.** LibreOffice does not evaluate formulas in hidden columns or hidden sheets at render time. A chart whose `series1.values` points at a hidden-column `SUMIFS` renders blank. Pattern: aggregate into a **visible** Summary sheet, point charts at Summary cells, hide only helper columns that are not chart sources.\n\n5. **Data-size-aware complexity.** A 10-row dataset does not get 5 KPIs and 4 charts. A 200-row dataset does not get 1 KPI and 1 chart. Scale up the composition with the input (table in §Design Ideas). Overbuilding is as wrong as underbuilding.\n\n## Requirements\n\nAll `officecli-xlsx` requirements apply (→ see officecli-xlsx §Requirements for Outputs). Dashboards add these:\n\n- **Dashboard sheet is the active tab on open.** Confirm 0-based sheet index with `officecli query \"$FILE\" sheet` BEFORE filling `activeTab=\"N\"`. Never guess the index.\n- **`calc.fullCalcOnLoad=true`.** Set via `officecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true`. Do NOT `raw-set` `<calcPr>` — it produces duplicate elements that fail validate.\n- **Refresh downstream cachedValue after every upstream edit.** `fullCalcOnLoad=true` schedules runtime recalc only; it does NOT refresh build-time `cachedValue`. After `set B=100 → set E==B+D → fix B=150`, E is stale until you re-issue E's formula (or close/reopen). Stale cache ships \"Net Change = 0\" to the board.\n- **Every chart has a descriptive title and every series has a name.** `\"Series1\"` in a legend is unfinished work.\n- **Every KPI value cell has a formula.** Verifiable: `officecli query \"$FILE\" 'Dashboard!:has(formula)' --json | jq '.data.results | length'` should equal your planned KPI count.\n- **Header row fill on every data sheet.** Data sheet, Summary sheet, and any secondary data sheet need row 1 filled (e.g., `fill=1F3864 + font.color=FFFFFF + font.bold=true`).\n- **10+ rows on Data sheet → ≥ 1 CF rule on a numeric column.** A 20-row table with zero visual scanning aid is a quality miss.\n- **Dashboard value columns sized to the widest expected cachedValue — not a fixed 22.** Rule of thumb at 24pt bold + currency numFmt: `width ≈ ceil((visible_chars + 2) × 1.3)`. A KPI holding `¥1,958,414,250` (14 visible chars with currency + commas) needs `width ≥ 28`; a 4-digit KPI still needs `width ≥ 22` as the floor. Hardcoding `22` for a 10+ digit KPI is how `###` ships to the user.\n- **Sparkline row height ≥ 20.** A sparkline in a default 15pt row is a flat squiggle — set `/Dashboard/row[N] height=22` (or 24 when paired with a 24pt KPI value cell in the same row).\n- **Print deliverables set `_xlnm.Print_Area` scoped to Dashboard** + hide non-Dashboard sheets + add `<pageSetup fitToPage/>`. Without all three, the print pipeline emits every sheet and Dashboard lands on page 2+. See §Print-ready delivery for the exact commands.\n\n## Quick Start\n\nMinimal viable dashboard: 12-month revenue CSV → 4 KPIs + 1 line chart + activeTab + fullCalcOnLoad. Adapt the numbers, don't copy-paste blind. Broken into phases so a single failed phase is obvious.\n\n**Phase 1 — Data sheet: create, import, format.**\n\n```bash\nFILE=my_dashboard.xlsx\nofficecli create \"$FILE\"\nofficecli import \"$FILE\" /Sheet1 --file sales.csv --header\nofficecli set \"$FILE\" '/Sheet1/col[A]' --prop width=12\nofficecli set \"$FILE\" '/Sheet1/col[B]' --prop width=15\nofficecli set \"$FILE\" '/Sheet1/B2:B13' --prop numFmt='$#,##0'\nofficecli set \"$FILE\" '/Sheet1/A1:B1' --prop fill=1F3864 --prop font.color=FFFFFF --prop font.bold=true\n```\n\n**Phase 2 — Dashboard sheet + one KPI card.**\n\n```bash\nofficecli add \"$FILE\" / --type sheet --prop name=Dashboard\nofficecli set \"$FILE\" '/Dashboard/col[A]' --prop width=22\nofficecli set \"$FILE\" '/Dashboard/col[B]' --prop width=12\nofficecli set \"$FILE\" /Dashboard/A1 --prop value=\"Total Revenue\" --prop font.size=9 --prop font.color=666666 --prop bold=true\nofficecli set \"$FILE\" /Dashboard/A2 --prop 'formula==SUM(Sheet1!B2:B13)' --prop numFmt='$#,##0' --prop font.size=24 --prop bold=true --prop font.color=2E7D32\n```\n\n**Phase 3 — Sparkline + chart.**\n\n```bash\nofficecli add \"$FILE\" /Dashboard --type sparkline --prop cell=B2 --prop range='Sheet1!B2:B13' --prop type=line --prop color=4472C4 --prop highPoint=true --prop highMarkerColor=FF0000\nofficecli add \"$FILE\" /Dashboard --type chart \\\n  --prop chartType=line \\\n  --prop title=\"Revenue Trend\" \\\n  --prop series1.name=\"Revenue\" \\\n  --prop series1.values='Sheet1!B2:B13' \\\n  --prop series1.categories='Sheet1!A2:A13' \\\n  --prop preset=dashboard --prop axisNumFmt='$#,##0' \\\n  --prop x=0 --prop y=5 --prop width=10 --prop height=15\n```\n\n**Phase 4 — fullCalcOnLoad → activeTab (LAST) → close → validate.**\n\n```bash\nofficecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true\n\n# Resolve Dashboard's 0-based index from the actual sheet list — never hardcode.\nDASH_IDX=$(officecli query \"$FILE\" sheet --json \\\n  | jq '[.data.results[].path] | index(\"/Dashboard\")')\nofficecli raw-set \"$FILE\" /workbook --xpath \"//x:sheets\" --action insertbefore \\\n  --xml \"<bookViews xmlns=\\\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\\\"><workbookView activeTab=\\\"$DASH_IDX\\\" /></bookViews>\"\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified end-to-end on a 12-row revenue CSV: `validate` reports no errors, Dashboard opens first, `Dashboard/A2.cachedValue` resolves (2,075,000 for the test data), chart renders with values linked.\n\n## Design Ideas\n\nOptions, not templates. The user's data and audience drive the choices.\n\n### Layout patterns (pick one, stay consistent)\n\n**Pattern 1 — executive summary** (board packs): KPI strip A1:H4, charts stack from row 6.\n```\n┌ KPI1 │ KPI2 │ KPI3 │ KPI4 ┐  rows 1-4\n├──────┴──────┴──────┴──────┤\n│     Chart 1 (wide)        │  rows 6-18\n├───────────────┬───────────┤\n│   Chart 2     │  Chart 3  │  rows 20-32\n```\n\n**Pattern 2 — ops console** (live ops): KPIs down A:B, charts fill C:L.\n```\n│ KPI1 │                   │\n│ KPI2 │    Chart 1        │  rows 1-12\n│ KPI3 │                   │\n│ KPI4 ├───────────────────┤\n│ KPI5 │    Chart 2        │  rows 14-26\n```\n\n**Pattern 3 — scorecard** (≥ 6 KPIs, no dominant chart): grid of 2×3 cards (label / value / sparkline).\n```\n│ KPI1 │ KPI2 │ KPI3 │  rows 1-4\n│ KPI4 │ KPI5 │ KPI6 │  rows 5-8\n```\n\n### Complexity scaling by data size\n\n| Rows | KPIs | Charts | Sparklines | CF rules | Preset |\n|---|---|---|---|---|---|\n| < 10 | 1–2 | 1 | skip | 0–1 | `minimal` |\n| 10–50 | 2–3 | 2 | only if sequential time-series | 1–2 | `dashboard` |\n| 50–200 | 3–5 | 2–3 | only if sequential time-series | 2–3 | `dashboard` |\n| 200+ | 3–5 | 3 | only if sequential time-series | 3–4 | `dashboard` |\n\n### Chart type selection\n\n| Data pattern | Chart type | Notes |\n|---|---|---|\n| Trend over time, one series | `line` | Add `trendline=linear` to show direction on noisy series |\n| Trend over time, multiple components | `line` (multi-series) or `columnStacked` | Stacked when components sum to a meaningful total |\n| Comparison across categories in time order | `column` | Not `bar` — horizontal bars break left-to-right time reading |\n| Part-of-whole breakdown | `doughnut` | Prefer over `pie`: `chartType=pie` has a known LibreOffice blank-render regression |\n| Budget vs actual | `combo` with `combosplit=1` | First series as bars, rest as lines |\n| Correlation | `scatter` | X-axis via `categories` / `series1.categories` — `series1.xValues` is UNSUPPORTED |\n\n### Preset options\n\n`--prop preset=<name>` on every chart. Options: `minimal`, `dashboard`, `corporate`, `magazine`, `colorful`, `monochrome`, `dark`. Pick one and stay consistent across all charts on a single Dashboard — mixing presets reads as accidental.\n\n### Conditional formatting — semantic colors\n\nFour CF rule types; each uses `--type <shorthand>` at `add` time:\n\n| Intent | `--type` | Typical props |\n|---|---|---|\n| Magnitude bar (sales, spend) | `databar` | `sqref=B2:B13 color=4472C4` — explicit `min=0 max=<plausible>` recommended for predictable scaling, but omitting them is valid (defaults to data min/max) |\n| Heat map (rates, growth) | `colorscale` | `sqref=D2:D13 mincolor=FFCDD2 midcolor=FFFFFF maxcolor=C8E6C9` |\n| Status indicator | `iconset` | `sqref=E2:E13 iconset=3Arrows` — see help for the full enum |\n| Custom business rule | `formulacf` | `sqref=B2:B13 'formula=$B2>=100000' fill=C8E6C9 font.color=2E7D32` — `font.bold` works on CF too |\n\nSemantic colors to stay consistent within a dashboard:\n\n- good / positive: fill `C8E6C9`, font `2E7D32`\n- bad / negative: fill `FFCDD2`, font `C62828`\n- neutral: fill `F5F5F5`, font `666666`\n\n### KPI card anatomy\n\nA card is a label cell + a value cell. The label is small gray (font.size=9, font.color=666666, bold); the value is large bold (font.size=24, bold=true, numFmt, font.color signals tone). One row of light fill (e.g. `F0F4FF`) across the card area gives the \"card\" read without building merged-cell scaffolds. Value column width must be sized to the largest cachedValue — never narrower than 22, often 26–32 for 8+ digit currency (see Requirements).\n\n### Chart width budget by title length\n\nAt the `dashboard` preset's default title font, the chart plot-box width (in column units) must stay ahead of the title string, or the title clips mid-word. Rule of thumb: `chart.width ≥ ceil(title.length × 0.18)`. A 35-character title (\"Department: Year-End Headcount vs Attrition Rate\") needs `width ≥ 7`; be safer and use 10–12. If the anchor cannot be widened, shorten the title to ≤ 25 characters — clipped titles in a board-ready deliverable are indefensible.\n\n`officecli get chart[N]` exposes numeric `width` (e.g. `width=480pt`) alongside `anchor` (e.g. `\"A6:K21\"`) at `.data.results[0].format`. Either is usable for Gate 2 — derive column span from anchor letters (A→K = 10 cols) when you need a column-unit budget.\n\n### Print-ready delivery (board-pack / investor-send / one-pager)\n\nTriggers: ask contains \"print\" / \"一页\" / \"董事会\" / \"投资人\". Four artefacts on the Dashboard sheet; non-Dashboard sheets hidden so the print pipeline emits one page only.\n\n```bash\n# 1. Print_Area scoped to Dashboard (xlnm convention).\nofficecli add \"$FILE\" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Dashboard --prop 'refersTo=Dashboard!$A$1:$H$36'\n# 2. fit-to-page on Dashboard.\nofficecli raw-set \"$FILE\" /Dashboard --xpath \"//x:worksheet\" --action prepend --xml '<sheetPr xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><pageSetUpPr fitToPage=\"1\"/></sheetPr>'\n# 3. Landscape page setup.\nofficecli raw-set \"$FILE\" /Dashboard --xpath \"//x:sheetData\" --action insertafter --xml '<pageSetup orientation=\"landscape\" paperSize=\"9\" fitToWidth=\"1\" fitToHeight=\"1\"/>'\n# 4. Hide non-Dashboard sheets — Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.\nfor S in Sheet1 Summary; do\n  officecli raw-set \"$FILE\" /workbook --xpath \"//x:sheet[@name='$S']\" --action setattr --xml \"state=hidden\" || true\ndone\n```\n\nDelete any `Print_Area` set on Data / Summary sheets — conflicting scopes emit multi-page output.\n\n## QA (REQUIRED — Delivery Gate)\n\n**Assume there are problems. Your job is to find them.** A chart that was rendered does not mean a chart that was meaningful. \"validate pass\" is not delivery; \"the Dashboard sheet reads like someone who knows the business made it\" is delivery.\n\n### Minimum cycle before \"done\"\n\nInherit the xlsx baseline (`view issues`, formula error queries, `validate`, HTML preview scan): → see officecli-xlsx §QA minimum cycle.\n\nThen run the dashboard-specific Delivery Gates. Each gate uses **COUNT-then-if** pattern with a `.data.*` wrapper — never chain `&& echo OK || echo FAIL`.\n\n**Gate 1 — KPI formula coverage.** Every planned KPI cell must carry a formula. Adjust `-lt 2` to your plan (4 KPIs → `-lt 4`).\n\n```bash\nKPI_FORMULAS=$(officecli query \"$FILE\" 'Dashboard!:has(formula)' --json | jq '.data.results | length')\n[ \"$KPI_FORMULAS\" -lt 2 ] && { echo \"REJECT Gate 1: $KPI_FORMULAS formula cells on Dashboard\"; exit 1; }\n```\n\n**Gate 2 — Chart count matches plan, every chart has data + plausible title width.**\n\n```bash\nCHART_COUNT=$(officecli query \"$FILE\" chart --json | jq '.data.results | length')\n[ \"$CHART_COUNT\" -lt 1 ] && { echo \"REJECT Gate 2: zero charts\"; exit 1; }\ncol_num () { local c=$1 n=0; for ((k=0;k<${#c};k++)); do n=$((n*26+$(printf '%d' \"'${c:$k:1}\")-64)); done; echo \"$n\"; }\nfor i in $(seq 1 \"$CHART_COUNT\"); do\n  JSON=$(officecli get \"$FILE\" \"/Dashboard/chart[$i]\" --json)\n  SC=$(echo \"$JSON\" | jq -r '.data.results[0].format.seriesCount // 0')\n  TITLE=$(echo \"$JSON\" | jq -r '.data.results[0].format.title // \"\"')\n  ANCHOR=$(echo \"$JSON\" | jq -r '.data.results[0].format.anchor // \"\"')\n  [ \"$SC\" = \"0\" ] || [ -z \"$TITLE\" ] && { echo \"REJECT Gate 2: chart[$i] seriesCount=$SC title='$TITLE'\"; exit 1; }\n  [ -z \"$ANCHOR\" ] && continue\n  LCOL=$(echo \"${ANCHOR%%:*}\" | sed 's/[0-9]*$//'); RCOL=$(echo \"${ANCHOR##*:}\" | sed 's/[0-9]*$//')\n  SPAN=$(( $(col_num \"$RCOL\") - $(col_num \"$LCOL\") + 1 ))\n  MIN=$(( (${#TITLE} * 18 + 99) / 100 ))\n  [ \"$SPAN\" -lt \"$MIN\" ] && { echo \"REJECT Gate 2: chart[$i] title=${#TITLE} chars needs width ≥ $MIN, anchor spans $SPAN\"; exit 1; }\ndone\n```\n\nNarrower titles at preset `minimal` / `magazine` may clip earlier than the 0.18 factor — spot-check.\n\n**Gate 3 — Chart series names populated (no \"Series1\" in legend).**\n\n```bash\nfor i in $(seq 1 \"$CHART_COUNT\"); do\n  BAD=$(officecli get \"$FILE\" \"/Dashboard/chart[$i]\" --json | jq '[.data.results[0].children[]? | select(.type == \"series\") | select((.format.name // \"\") | test(\"^Series[0-9]+$\"; \"i\"))] | length')\n  [ \"$BAD\" -gt 0 ] && { echo \"REJECT Gate 3: chart[$i] has $BAD auto-named series\"; exit 1; }\ndone\n```\n\n**Gate 4 — CF rules on Data sheet (10+ rows).**\n\n```bash\nCF_COUNT=$(officecli query \"$FILE\" conditionalformatting --json | jq '.data.results | length')\n[ \"$CF_COUNT\" -lt 1 ] && { echo \"REJECT Gate 4: zero CF rules on 10+ row data sheet\"; exit 1; }\n```\n\nNote: `query conditionalformatting` is the canonical element name; `query cf` returns 0 (not an alias).\n\n**Gate 5 — activeTab and fullCalcOnLoad set.** Compare against real Dashboard index (Dashboard-at-index-0 is a true pass).\n\n```bash\nDASH_IDX=$(officecli query \"$FILE\" sheet --json | jq '[.data.results[].path] | index(\"/Dashboard\")')\nACTIVE=$(officecli get \"$FILE\" /workbook --json | jq '.data.results[0].format.activeTab // -1')\nFULLCALC=$(officecli get \"$FILE\" /workbook --json | jq -r '.data.results[0].format[\"calc.fullCalcOnLoad\"] // false')\n[ \"$ACTIVE\" != \"$DASH_IDX\" ] && { echo \"REJECT Gate 5: activeTab=$ACTIVE Dashboard=$DASH_IDX\"; exit 1; }\n[ \"$FULLCALC\" != \"true\" ] && { echo \"REJECT Gate 5: calc.fullCalcOnLoad=$FULLCALC — stale caches will ship\"; exit 1; }\n```\n\n**Gate 6 — Placeholder sweep.** No build-time tokens in rendered output.\n\n```bash\nLEAKS=$(officecli view \"$FILE\" text 2>/dev/null | grep -niE '\\{\\{|\\$fy\\$|<TODO>|xxxx|TBD' | wc -l | tr -d ' ')\n[ \"$LEAKS\" -gt 0 ] && { echo \"REJECT Gate 6: $LEAKS placeholder tokens\"; exit 1; }\n```\n\n**Gate 7 — Visual delivery floor (ported from xlsx).** Run `officecli view \"$FILE\" html` and Read the returned HTML path. Confirm:\n\n- No `###` in any Dashboard or Data cell (columns too narrow).\n- No truncated KPI labels, sheet tab names, or chart titles.\n- No placeholder tokens rendered as text (`$fy$24`, `{var}`, `<TODO>`, `xxxx`).\n- Pie / doughnut slices render with distinct fill colors (if collapsed in LibreOffice, verify in the user's target viewer before declaring broken — → see officecli-xlsx §Known Issues/Renderer caveats).\n- No empty chart anchors — every chart has a visible, plausible plot.\n- Dashboard sheet opens first (tab highlighted, active area scrolled to top).\n\nIf `view html` is blocked (renderer conflict, headless, port busy), Gate 7 is still **mandatory** — run ALL fallback checks:\n\n```bash\n# a) Token / ### sweep.\nofficecli view \"$FILE\" text 2>/dev/null | grep -nE '###|\\{\\{|<TODO>|\\$fy\\$|xxxx' && { echo \"REJECT Gate 7: tokens or ### present\"; exit 1; }\n# b) Per-KPI: cachedValue length × coef must fit col width. coef=0.55 fit-to-page, 0.85 otherwise.\nfor CELL in A2 C2 E2 G2; do\n  CV=$(officecli get \"$FILE\" \"/Dashboard/$CELL\" --json | jq -r '.data.results[0].format.cachedValue // .data.results[0].text // \"\"')\n  W=$(officecli get \"$FILE\" \"/Dashboard/col[${CELL%%[0-9]*}]\" --json | jq -r '.data.results[0].format.width // 0')\n  CAP=$(echo \"$W * 0.55\" | bc -l | awk '{print int($1)}')\n  [ \"${#CV}\" -gt \"$CAP\" ] && { echo \"REJECT Gate 7: $CELL '$CV' (${#CV} chars) > cap $CAP\"; exit 1; }\ndone\n# c) Rerun Gate 2 title × 0.18 ≤ anchor span.  d) Log which fallback was used and why.\n```\n\nGate 7 must **NEVER** be skipped — skipping ships `###` to the user.\n\nIf scene keywords include print / 一页 / board / 投资人 / 董事会, extend Gate 7 with a structural print-scope check:\n\n```bash\nif echo \"$USER_REQ\" | grep -qiE 'print|一页|投资人|董事会|board'; then\n  # Every non-Dashboard sheet must be hidden or veryHidden.\n  # query sheet --json carries the name in .preview (no .name/.state); visibility lives in\n  # each sheet's own get .format.hidden (absent => visible), so iterate + probe per sheet.\n  LEAKING=\"\"\n  while IFS=$'\\t' read -r SPATH SNAME; do\n    [ \"$SNAME\" = \"Dashboard\" ] && continue\n    HIDDEN=$(officecli get \"$FILE\" \"$SPATH\" --json | jq -r '.data.results[0].format.hidden // false')\n    [ \"$HIDDEN\" != \"true\" ] && LEAKING=\"$LEAKING $SNAME\"\n  done < <(officecli query \"$FILE\" 'sheet' --json | jq -r '.data.results[] | [.path, .preview] | @tsv')\n  [ -n \"$LEAKING\" ] && { echo \"REJECT Gate 7 print-scope: visible non-Dashboard sheet(s):$LEAKING — hide before delivery\"; exit 1; }\n  # Dashboard must carry an explicit Print_Area named range.\n  PA=$(officecli query \"$FILE\" 'namedrange[name=\"_xlnm.Print_Area\"]' --json | jq '.data.results | length')\n  [ \"$PA\" -ge 1 ] || { echo \"REJECT Gate 7 print-scope: no _xlnm.Print_Area set\"; exit 1; }\nfi\n```\n\nThe user opens the file in their target viewer (Office / WPS / Numbers) for the final print preview — the skill does not render export artefacts.\n\n**Gate 8 — Formula sanity (cachedValue real, not stale/error).** `fullCalcOnLoad=true` refreshes at runtime, NOT build-time cache — so every formula cell must carry a non-empty, non-zero, non-error `cachedValue` now.\n\n```bash\nfor CELL in A2 C2 E2 G2; do\n  JSON=$(officecli get \"$FILE\" \"/Dashboard/$CELL\" --json)\n  [ -z \"$(echo \"$JSON\" | jq -r '.data.results[0].format.formula // \"\"')\" ] && continue\n  CV=$(echo \"$JSON\" | jq -r '.data.results[0].format.cachedValue // \"\"')\n  case \"$CV\" in\n    \"\" | \"0\" | \"#DIV/0!\" | \"#REF!\" | \"#N/A\" | \"#VALUE!\" | \"#NAME?\" | \"null\")\n      echo \"REJECT Gate 8: $CELL cachedValue='$CV' — re-issue formula or close+reopen\"; exit 1 ;;\n  esac\ndone\n```\n\nIf a KPI is genuinely zero (e.g. \"terminations this quarter\" = 0), whitelist it in the loop and document — default assumption is \"zero is broken\".\n\nIf anything fails, fix at source, re-run the full cycle.\n\n### Honest limits\n\nScatter charts do not accept `series1.xValues` (UNSUPPORTED) — feed the x-axis via `categories` / `series1.categories`. LibreOffice chart color drift / pie-slice collapse / checkbox double-box are viewer artifacts — spot-check in Office / WPS / Numbers first.\n\n## Reference\n\n- **Shorthand `--type` at `add`:** `chart`, `sparkline`, `databar`, `colorscale`, `iconset`, `formulacf`. CF rules map to `help xlsx conditionalformatting`; path suffix `/Sheet/cf[N]`.\n- **Full schemas live in help:** `officecli help xlsx chart` / `sparkline` / `conditionalformatting`. This skill does not mirror them.\n- **DeferredAddKeys (add-only):** `combosplit`, `holesize`. See D-1. (`preset`, `trendline`, `referenceline`, `axisNumFmt` now work on `set` too — help shows `[add/set]`.)\n- **Build order:** charts + sparklines + CF + tabColors first → `calc.fullCalcOnLoad=true` via high-level `set` → `raw-set activeTab` **LAST** (after all sheets exist).\n\n## Known Issues & Pitfalls\n\n### Dashboard-specific\n\n| # | Issue | Mitigation |\n|---|---|---|\n| D-1 | `combosplit` is a DeferredAddKey — works on `add` only. (`preset`, `referenceline`, `trendline`, `axisNumFmt` now apply on `set` too — help shows `[add/set]`.) | Set `combosplit` at `add` time; cannot apply after the fact — remove + re-add. The other four can be applied or changed post-creation via `set`. |\n| D-2 | `referenceline` format is `value:color:label:dash` (color BEFORE label). `\"0:Break-Even:FF0000:dash\"` fails `Invalid color value`. | Order is value, color, label, dash. |\n| D-3 | Scatter charts do NOT accept `series1.xValues` (UNSUPPORTED). Feed the x-axis through `categories` / `series1.categories`. | `--prop series1.categories=\"Sheet1!A2:A13\"` (or `--prop categories=\"Sheet1!A2:A13\"`) |\n| D-4 | `formulacf` honors `font.bold` / `font.italic` (written to the dxf font and surfaced on readback), alongside `fill` and `font.color`. | Use any of `fill` / `font.color` / `font.bold` / `font.italic` to signal a CF rule. |\n| D-5 | Dashboard column widths default to 8.43 — KPI values at 24pt bold show `###` | Size by cachedValue bracket: 4–6 digits → 22–24; 7–9 digits (million) → 26–30; 10+ digits (亿 / billion) → 32–36; 百亿 / 10-digit + currency symbol + fit-to-page landscape → **40–44**. Formula `ceil((visible_chars+2)*1.3)` is a starting point; always verify via Gate 7 fallback b). Sparkline columns: 12. |\n| D-6 | `raw-set activeTab` must be the LAST mutation. Inserting before all sheets exist shifts indices. | Finish all sheets / charts / CF / sparklines / tabColors, then `raw-set`. |\n| D-7 | `calc.fullCalcOnLoad` via `raw-set` creates duplicate `<calcPr>` → validate fails | Use `officecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true`. |\n| D-8 | LibreOffice does not evaluate hidden-column formulas at render → charts referencing hidden cells render blank | Aggregate into a visible Summary sheet, chart reads from Summary. Hide only columns that are not chart sources. |\n| D-9 | `chartType=pie` blank-renders in LibreOffice | Use `doughnut` as the safe substitute for part-of-whole breakdowns. |\n| D-10 | `SUMIFS` / `AVERAGEIFS` with date criteria fails silently if the criterion is a string | Wrap with `DATE()` or `DATEVALUE()`: `=SUMIFS(B2:B13,A2:A13,DATE(2025,1,5))`. |\n| D-11 | Summary sheet percentage formulas display as raw decimals (0.098) without `numFmt` | Set `numFmt=\"0.0%\"` at the same `set` call as the formula. |\n| D-12 | `import --header` sets freeze + AutoFilter but does NOT set column widths. | Set widths on `col[]`. `numFmt` on a `col[]` path now applies a column-level style (`<col s=...>`, schema-valid, reads back as `numberformat`); it formats blank cells in the column. Cells with their own style still need a per-cell-range `numFmt`. |\n| D-13 | Sparkline `highpoint` is a bool (highlight on/off), not a color. `--prop highpoint=FF0000` errors `Invalid boolean value` | `--prop highPoint=true --prop highMarkerColor=FF0000`. Same pattern for lowPoint / firstPoint / lastPoint and their *MarkerColor. |\n| D-14 | Sparkline cross-sectional data is meaningless (a region or department has no ordering) | Skip sparklines unless rows are a sequential time-series (dates, months, quarters). |\n| D-15 | Empty chart `add` is rejected (`Chart requires a 'data' property`) at the CLI layer — legacy skills that relied on silent accept will fail here | Always provide `series1.values=` / `dataRange=` / inline `data=` at chart `add` time. Treat Gate 2 seriesCount check as a belt-and-braces verification. |\n| D-16 | `fullCalcOnLoad=true` guarantees a **runtime** recalc when the end user opens the file; it does NOT refresh the build-time `cachedValue` in XML. Build sequence `set B=100 → set E==B+D → fix B=150` leaves `E.cachedValue` stale (board sees \"Net Change = 0\"). | After all upstream edits are final, re-issue every downstream formula (`officecli set \"$FILE\" /Sheet/E2 --prop formula==B2+D2`) OR `close` + re-open the file. Gate 8 verifies. |\n| D-17 | The built-in calc engine does NOT evaluate `SUMPRODUCT` with array-predicate form `SUMPRODUCT((A2:A97=X)*C2:C97*D2:D97)` — cachedValue stays `0`/`null`, Gate 8 rejects. Runtime Excel / WPS compute fine, but board-delivered XLSX with stale cache still ships `0`. | Rewrite as helper column + `SUMIF`: `F2==C2*D2` on source sheet, then `=SUMIF(B:B, \"Region X\", F:F)`. Or pre-aggregate in Summary sheet and chart from there. |\n\n### Inherited (pointer only)\n\nCross-sheet `!` trap, chart `anchor` / series immutability after create → see officecli-xlsx §Known Issues.\n","skills/officecli-docx/SKILL.md":"---\nname: officecli-docx\ndescription: \"Use this skill any time a .docx file is involved -- as input, output, or both. This includes: creating Word documents, reports, letters, memos, or proposals; reading, parsing, or extracting text from any .docx file; editing, modifying, or updating existing documents; working with templates, tracked changes, comments, headers/footers, or tables of contents. Trigger whenever the user mentions 'Word doc', 'document', 'report', 'letter', 'memo', or references a .docx filename.\"\n---\n\n# OfficeCLI DOCX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good docx looks like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help docx                         # List all docx elements\nofficecli help docx <element>               # Full element schema (e.g. paragraph, field, numbering, watermark, toc)\nofficecli help docx <verb> <element>        # Verb-scoped (e.g. add field, set section)\nofficecli help docx <element> --json        # Machine-readable schema\n```\n\nHelp is pinned to the installed CLI version. When this skill and help disagree, **help is authoritative**.\n\n## Mental Model\n\nA `.docx` is a ZIP of XML parts (`document.xml`, `styles.xml`, `numbering.xml`, `header*.xml`, `footer*.xml`, `comments.xml`, …). Everything the user sees — headings, tables, page numbers, TOC, tracked changes — is XML inside that ZIP. `officecli` gives you a semantic-path API (`/body/p[1]/r[2]`) over it, so you almost never touch raw XML; when you must, use `raw-set` (see the XML appendix).\n\n## Shell & Execution Discipline\n\ndocx paths contain `[]`; some prop values contain `$`. Both are shell metacharacters. Escaping happens at three layers — keep them separate.\n\n1. **Shell.** ALWAYS quote element paths: `\"/body/p[1]\"`, not `/body/p[1]` (zsh/bash glob `[N]`). Single-quote any value containing `$`: `--prop text='$50M'` — at any length, the whole value inside one pair of single quotes. Unquoted `$50M` is stripped to `M`; mixing `'…$var…'` and `\"…$50…\"` on one long string is where the `$50` silently vanishes.\n2. **CLI (`text=`).** The two-char escapes `\\n`, `\\v` and `\\t` ARE interpreted in `--prop text=` (same rule as pptx): `\\n` starts a **new paragraph** (each new `<w:p>` inherits the source paragraph's style), `\\v` is a **soft line break** within one paragraph (`<w:br/>`), `\\t` a `<w:tab/>`. Double the backslash (`\\\\n`) for a literal backslash-n (rarely wanted). Row-level table shortcuts `c1…cN` are the exception: there `\\n` is a `<w:br/>` line break inside the cell (see recipe (e)).\n3. **JSON (batch).** Batch does NOT interpret the two-char escapes — a JSON `\"\\\\n\"` or `\"\\\\v\"` lands as literal text. Use JSON escapes for the real characters: `\"\\n\"` (a newline) starts a new paragraph, `\"\\u000b\"` is a soft line break.\n\nIf in doubt, `view text` after writing and compare character-for-character.\n\n**Incremental execution.** `officecli` mutates the file on every call. Run commands one at a time and check each exit code — a 50-command script that fails at command 3 cascades silently. After any structural op (new style, table, TOC, section break) run `get` on it before stacking more.\n\n**Open/save lifecycle:** `officecli open <file>` at the start, `officecli save <file>` at the end to flush to disk — `save` only writes and leaves the resident warm for follow-up edits; reach for `officecli close <file>` only to release the resident on a one-shot handoff. Both are always safe (never error or lose work). For many paragraphs of one style, use `batch` (one pass for the whole array). **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (python-docx, Word, a renderer, delivery).\n\n**`$FILE` convention.** All commands use `\"$FILE\"` — set it once (`FILE=\"your-doc.docx\"`). Never copy a literal `doc.docx` / `review.docx` into output — always substitute your actual target.\n\n## Requirements for Outputs\n\nDeliverable standards every document MUST meet — know these before reaching for a command.\n\n**Clear hierarchy.** Every non-trivial document has Title → Heading 1 → Heading 2 → body, not a wall of unstyled `Normal` paragraphs. If `view outline` shows one flat list, the hierarchy is missing.\n\n**Explicit heading sizes** (Word default style sizes drift between templates): **H1 ≥ 18pt** (20pt for long reports), H2 = 14pt bold, H3 = 12pt bold, body = 11–12pt, line spacing 1.15–1.5x. Prefer `style=Heading1` over inline sizes so a retheme touches the definition once — but set explicit sizes when you can't trust the template's styles.\n\n**One body font, one accent.** One readable body font (Calibri, Cambria, Georgia, Times New Roman); accent color for heading emphasis or table headers, not rainbow formatting.\n\n**Spacing through properties.** Use `spaceBefore` / `spaceAfter` on paragraphs. Rows of empty paragraphs break pagination and are flagged by `view issues`.\n\n**Typographic quality.** New content uses curly quotes (`'` `'` `\"` `\"`), not ASCII — Unicode directly or XML entities (`&#x2018;`/`&#x2019;`/`&#x201C;`/`&#x201D;`) inside `raw-set`. En-dash `–` for ranges (`2024–2026`), em-dash `—` for parenthetical breaks.\n\n**Headers, footers, page numbers on any document > 1 page.** Page numbers go through a live `PAGE` field (`--prop field=page`), never the literal text \"Page 1\" — the CLI injects `<w:fldChar>` for you (see Headers & Footers).\n\n**Preserve existing templates.** When editing a file that already has a look, match it — existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY document)\n\nBefore declaring done, run `officecli view \"$FILE\" html` and Read the returned HTML path to confirm ALL of these:\n\n- **No placeholder tokens rendered as data.** `$xxx$`, `{var}`, `{{name}}`, `<TODO>`, `lorem`, `xxxx` must never appear in a heading, body, cover, TOC, caption, header, or footer. A literal `{name}` meant for a human to fill belongs inside a visible instruction paragraph (\"Replace `{name}` before sending\"), never as finished content.\n- **No truncated titles or overflowing cells.** Widen the column or set `wrapText` rather than trimming content.\n- **TOC present when the document has 3+ headings** (`--type toc`).\n- **Cover page ≥ 60% filled, last page ≥ 40% filled.** Pad a thin cover with subtitle / author / date / scope / key highlights; pad a \"Thank you\" last page with conclusion / next steps / contact / legal.\n- **No `\\$`, `\\t`, `\\n` literals in document text.** If `view text` shows these, a shell-escape layer leaked — delete the paragraph and re-enter it.\n\nIf any fails, STOP and fix before declaring done.\n\n## Common Workflow\n\nSix steps. Every non-trivial build follows this shape.\n\n1. **Open the file.** `officecli open \"$FILE\"` (resident default). New file: `officecli create \"$FILE\"` first.\n2. **Orient.** Existing file: `officecli view \"$FILE\" outline` — heading tree, section count, whether a TOC / watermark / tracked changes already exist. Never edit blind.\n3. **Build incrementally.** Structural first, content next, formatting last: styles & numbering defs → sections / page setup → headings & body → tables / images / fields / TOC → headers / footers → comments. After each structural op, `get` it back before stacking on top.\n4. **Format to spec.** Explicit heading sizes, spacing, widths, alignment, tabs, list indents — formatting is part of the deliverable, not optional polish.\n5. **Save, then trust structure over cached text.** `officecli save \"$FILE\"` writes the XML. TOC / PAGE / NUMPAGES / SEQ / PAGEREF fields carry **cached values** that may be stale or empty until a human recalculates (F9 in Word). Confirm fields *exist* (`get --depth 3` finds `<w:fldChar>`) rather than trusting the visible text.\n6. **QA — assume there are problems.** You are done after one fix-and-verify cycle finds zero new issues, not when your last command exited 0. See QA.\n\n## Quick Start\n\nMinimal viable docx: a heading, a body paragraph, a subheading, and a footer with a live page-number field. Adapt, don't copy-paste — your file, your content.\n\n```bash\nFILE=\"review.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Q4 2026 Review\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Revenue grew 18% year-over-year, ahead of plan.\" --prop size=11pt --prop spaceAfter=8pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Key Drivers\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Enterprise renewals, upsell, and a new EMEA region.\" --prop size=11pt\nofficecli add \"$FILE\" / --type footer --prop type=default --prop size=9pt --prop text=\"Page \" --prop field=page\nofficecli set \"$FILE\" \"/footer[1]/p[1]\" --prop align=center\nofficecli save \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified: `validate` returns `no errors found`; `get /footer[1] --depth 3` shows the 5-run PAGE field chain (begin / instrText / separate / cached value / end).\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` tells you what's already there; jump into `view text` / `get` / `query` once you know where to look.\n\n```bash\nofficecli view \"$FILE\" outline            # heading tree, section count, table/image counts, watermark, tracked-changes presence — orient here first\nofficecli view \"$FILE\" html               # Read the returned HTML path: first visual check after a batch of edits (hierarchy, empty-para spacing, missing TOC)\nofficecli view \"$FILE\" text --start 1 --end 80   # text for content QA; paths shown as [/body/p[N]] so you can jump back with get\nofficecli view \"$FILE\" annotated          # values + style/font/size + warnings per run\nofficecli view \"$FILE\" stats              # paragraph counts, font usage, style distribution\nofficecli view \"$FILE\" issues             # empty paras, missing alt text, spacing anomalies\n```\n\n`officecli watch \"$FILE\"` keeps a live preview running for the human user to open at their discretion — agent self-check uses `view html`. Final visual verification is the user opening the `.docx` in Word / WPS / Pages.\n\n**Inspect one element.** XPath-style semantic paths (1-based). Always quote — shells glob `[N]`. Use `[last()]` (with parens) for the last element; `[last]` errors. Add `--json` for machine output.\n\n```bash\nofficecli get \"$FILE\" /                          # document root: metadata, page setup\nofficecli get \"$FILE\" \"/body/p[1]\"                # one paragraph\nofficecli get \"$FILE\" \"/body/p[1]/r[1]\"           # one run (character-level formatting)\nofficecli get \"$FILE\" \"/body/tbl[1]\" --depth 3    # table with rows and cells\nofficecli get \"$FILE\" \"/footer[1]\" --depth 3      # footer — check for fldChar\nofficecli get \"$FILE\" \"/styles/Heading1\"          # style definition\nofficecli get \"$FILE\" /numbering --depth 2        # numbering abstractNum + num bindings\n```\n\n**Query across the document.** CSS-like selectors, for systematic checks rather than hand-walking. Operators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists). Full reference: `officecli query --help`.\n\n```bash\nofficecli query \"$FILE\" 'paragraph[style=Heading1]'       # all H1s\nofficecli query \"$FILE\" 'p:contains(\"quarterly\")'         # text match\nofficecli query \"$FILE\" 'p:empty'                         # empty paragraphs (clutter)\nofficecli query \"$FILE\" 'image:no-alt'                    # accessibility gaps\nofficecli query \"$FILE\" 'paragraph[size>=24pt]'           # numeric comparison\nofficecli query \"$FILE\" 'field[fieldType!=page]'          # fields other than PAGE\n```\n\n`query --json` wraps results in `.data.results[]` — `jq '.data.results | length'` to count.\n\n**Large documents.** Navigate by heading with `view outline` and jump with `query`; don't dump the whole body into context.\n\n## Creating & Editing\n\nVerbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`, `raw-set` (last-resort XML). Ninety percent of a build is paragraphs, runs, tables, a couple of images, a TOC, and a footer.\n\n### Paragraphs, runs, styles\n\nA paragraph (`p`) is a block; a run (`r`) is a span of consistent character formatting inside it. Set paragraph-level props (style, alignment, spacing, indent) on the `p`; set font / size / color / bold on the `r`.\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1 --prop size=18pt --prop bold=true --prop spaceAfter=12pt\nofficecli set \"$FILE\" \"/body/p[1]/r[1]\" --prop color=1F4E79\n```\n\nUse `spaceBefore` / `spaceAfter` for vertical spacing — never chains of empty paragraphs. For left indent use `--prop indent=720` (twips), `firstLineIndent=360` for first line, `hangingIndent=720` for hanging; leading spaces fire `view issues`.\n\n### Tables\n\nTables are `/body/tbl[N]` with rows `tr[N]` and cells `tc[N]`. Add with row/column counts, then fill.\n\n```bash\nofficecli add \"$FILE\" /body --type table --prop rows=4 --prop cols=3 --prop width=100%\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop header=true --prop c1=Quarter --prop c2=\"Revenue\" --prop c3=\"Growth\"\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[1]/r[1]\" --prop bold=true\n```\n\nRow-level `set` supports `height`, `header`, and `c1 / c2 / … / cN` text shortcuts (`cN` generalises to any column count). Cell formatting (bold, fill, color) goes on the cell's paragraph / run — **not** row-level. For per-cell borders, set cell-level `border.*` on the `tc` (`--prop border.bottom=\"single;6;000000;0\"`), or paragraph-level `pbdr.*` on the inner paragraph.\n\n**Horizontal rule = a paragraph bottom border, never a 1-row table.** A table-as-divider renders as an empty min-height box (worst in headers/footers). Use `pbdr.bottom` (`STYLE;SIZE;COLOR`) on the paragraph instead:\n\n```bash\nofficecli set \"$FILE\" \"/body/p[3]\" --prop pbdr.bottom=\"single;6;2E75B6\"\n```\n\n### Lists (bullets, numbered, multi-level)\n\nFor single-level bullets/numbers, set `listStyle` on the paragraph (`listStyle` is a paragraph prop, NOT a run prop — common mistake):\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"First item\" --prop listStyle=bullet\n```\n\nFor multi-level (legal-style 1 / 1.1 / 1.1.1), add an `abstractNum`, then a `num`, then reference the `numId` per paragraph:\n\n```bash\nofficecli add \"$FILE\" /numbering --type abstractnum --prop format=decimal     # → abstractNum id=0\nofficecli add \"$FILE\" /numbering --type num --prop abstractNumId=0             # → num id=1\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Section one\" --prop numId=1 --prop ilvl=0\n```\n\nIDs are 0-based: the first `abstractNum` is id=0; the `num` references it via `abstractNumId=0` and is itself assigned id=1. A non-existent `abstractNumId` errors, so check ids after creating. Verify with `officecli query \"$FILE\" 'paragraph[numId>0]'`. See `help docx abstractnum` / `help docx num` for level and format options.\n\n### Tab stops (signature lines, leader rows)\n\nTab stops are a first-class `tab` child of the paragraph; `pos` accepts `6in`/`6cm`/twips, `val` ∈ `left`/`center`/`right`, `leader` ∈ `none`/`dot`/`hyphen`/`underscore`. See `help docx tab`.\n\n```bash\nofficecli add \"$FILE\" \"/body/p[1]\" --type tab --prop pos=6in --prop val=right --prop leader=dot\n```\n\n**Leader caveat.** `leader=dot` alone emits no dots — the leader renders only when a real `<w:tab/>` character sits in a run between the text and the tab stop. Put one there with `\\t` in the text: define the stop (`add tab --prop pos=6in --prop val=right --prop leader=dot`), then `--prop text=\"Chapter 1\\t12\"` — the `\\t` becomes the `<w:tab/>` and dots fill to the right-aligned page number. (Literal `text=\"Chapter 1 ......... 12\"` also ships, but a real tab stop aligns cleanly.)\n\n### Fields (PAGE / NUMPAGES / DATE / MERGEFIELD / REF)\n\nFields are live values computed at render time. `fieldType` picks the field; `name` supplies the target (merge name or `ref` bookmark); `format` / `instr` add switches.\n\n| Field | Use | Example |\n|---|---|---|\n| `page` | current page number | `--prop field=page` on footer, or `--prop fieldType=page` inline |\n| `numpages` | total pages | `--prop field=numpages` / `--prop fieldType=numpages` |\n| `date` | today | `--prop fieldType=date --prop format='yyyy-MM-dd'` |\n| `mergefield` | template merge token | `--prop fieldType=mergefield --prop name=CustomerName` |\n| `ref` | cross-reference to a bookmark | `--prop fieldType=ref --prop name=bookmarkName` |\n\nFull `fieldType` enum (30+ values incl. `pageref`, `seq`, `styleref`, `docproperty`, `createdate`, …) is in `help docx field`. **There is NO `fieldInstr` fieldType** — use the `instruction` prop for raw field instruction text when typed shortcuts fall short. Picture switches (`MERGEFIELD Amount \\# \"#,##0.00\"`, `DATE \\@ \"yyyy年MM月\"`) go via `--prop instruction='…'` (mergefield's `format` prop is ignored with a warning — use `instruction`).\n\n```bash\nofficecli add \"$FILE\" \"/body/p[3]\" --type field --prop fieldType=mergefield --prop name=customer_name\n# Renders «customer_name» — visible placeholder, replaced in Word at mail-merge time.\n```\n\n**MERGEFIELD templates: never render placeholder literals.** A `{{customer_name}}` or `$NAME$` shown as body text is a failed template the recipient sees — insert a real MERGEFIELD (above), or confine literal tokens to an obvious instruction paragraph. Confirm with `query 'field[fieldType=mergefield]'`.\n\n**SEQ / PAGEREF / TOC field values.** officecli doesn't store rendered field values at write time. Recompute by what each path needs:\n\n- **SEQ numbering** (`Figure 1/2/3`): `officecli set \"$FILE\" / --prop recalcFields=seq` counts SEQ fields in body document order and writes the cached values (`evaluated` flips true; switches/formats in `help docx document`). Heading-relative `\\s` and SEQ in headers/footers defer to Word.\n- **PAGE / PAGEREF / NUMPAGES / TOC page numbers** need pagination, which officecli has no engine for — `officecli set \"$FILE\" /settings --prop updateFields=true` defers them to Word on open.\n\nUse both on a multi-figure document. Academic papers: see the `officecli-academic-paper` skill.\n\n### Headers & Footers (page numbering)\n\nSingle-command pattern — the CLI injects `<w:fldChar>`, so you never compose the field by hand:\n\n```bash\n# Empty first-page footer — auto-enables differentFirstPage so the cover has no page number\nofficecli add \"$FILE\" / --type footer --prop type=first --prop text=\"\"\n# Default footer with live page number\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=9pt --prop text=\"Page \" --prop field=page\n```\n\nWhen both exist, the default footer is `/footer[2]`; alone it is `/footer[1]`. **Verify**: `get --depth 3` must show `fldChar` children, not just a run with literal `\"Page\"` (`view outline` prints \"Footer: Page\" for both live fields AND static text — don't rely on it). Do NOT `set --prop differentFirstPage=true` — that prop is unsupported (exit 2 with `WARNING: UNSUPPORTED`; the element itself is still created — check `success` in `--json`); adding a first-type footer flips the bit. For composite **Page X of Y**, see recipe (b).\n\n### Table of Contents\n\nFor any document with 3+ headings, first verify that the requested range has real sources. A TOC source must use a built-in Heading style or a custom paragraph style with `outlineLvl`; bold or large Normal text is not a source. If no eligible source exists, stop and fix the heading structure instead of inserting a TOC that renders \"Error! No table of contents entries found.\"\n\n```bash\n# Built-in Heading styles are TOC sources.\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Introduction\" --prop style=Heading1\n# Custom paragraph styles need an outline level (0 = Heading 1).\nofficecli add \"$FILE\" /styles --type style --prop id=ThesisH1 --prop type=paragraph --prop outlineLvl=0\n# Add the TOC only after its sources exist.\nofficecli add \"$FILE\" /body --type toc --prop levels=\"1-3\" --prop title=\"Table of Contents\" --prop hyperlinks=true --index 0\n```\n\nPage numbers need pagination; OfficeCLI cannot calculate TOC page numbers itself. Set `updateFields=true` so Word recomputes the TOC (and all fields) on open. Without a Word-compatible field engine, report the TOC as dynamic and uncomputed; do not claim ready page numbers or guess a static TOC.\n\n```bash\nofficecli set \"$FILE\" /settings --prop updateFields=true\n```\n\nAddress the TOC directly with `/toc[1]` or `/tableofcontents` for `get`/`set`/`remove`.\n\n### Images\n\nPictures go inside a run. Alt text is mandatory for accessibility — pass `alt` directly at create time:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[5]\" --type picture --prop src=logo.png --prop width=1.5in --prop alt=\"Acme logo\"\n```\n\nConfirm `officecli query \"$FILE\" 'image:no-alt'` is empty before delivery.\n\n### Charts\n\nFor data, add a **native chart** — editable, themeable, accessible, re-renders in Word — never a flat PNG screenshot of a chart. `data=\"Label:v1,v2,…\"` per series; one `data=` per series (or `series1=`/`series2=`).\n\n```bash\nofficecli add \"$FILE\" /body --type chart --prop chartType=bar --prop title=\"Revenue by Region\" --prop categories=\"EMEA,APAC,Americas\" --prop data=\"2026:120,150,180\"\n```\n\n`chartType` ∈ bar / column / line / pie / area / scatter (`help docx chart` for axis/legend/series styling). A PNG via `--type picture` is only a fallback for an exotic chart officecli can't build.\n\n### Hyperlinks and bookmarks\n\nExternal links go via `hyperlink`:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[2]\" --type hyperlink --prop url=\"https://example.com\" --prop text=\"our site\"\n```\n\n**Internal links** (to a bookmark) use `--prop anchor=bookmarkName` — not a `#fragment` in `url`:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[2]\" --type hyperlink --prop anchor=chapter1 --prop text=\"See Chapter 1\"\n```\n\nPairing a `PAGEREF` field with visible text is the alternative. See `help docx hyperlink` / `help docx bookmark`.\n\n### Sections and page setup\n\nDocument root `/` carries page setup (`pageWidth`, `pageHeight`, margins, in twips). Multi-section documents (landscape insert, columns) add a `section` break — see `help docx section`. Both camelCase (`pageWidth`, canonical) and lowercase alias (`pagewidth`) are accepted; prefer camelCase.\n\n```bash\nofficecli set \"$FILE\" / --prop pageWidth=12240 --prop pageHeight=15840 --prop marginTop=1440 --prop marginLeft=1440\n# Newspaper-style multi-column flow (columnSpace in twips; 720 = 0.5in):\nofficecli set \"$FILE\" / --prop columns=2 --prop columnSpace=720\n```\n\n### Forcing page breaks\n\nUse exactly one page-break mechanism per logical boundary. The default is `pageBreakBefore=true` on the target heading. Alternatively, insert one explicit `pagebreak` before the heading. Never combine both mechanisms for the same boundary: the resulting double break can create a blank page. Never set `pageBreakBefore` on `p[last()]` immediately after adding a `pagebreak`.\n\n```bash\n# Default: apply the break directly to the heading.\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Introduction\" --prop style=Heading1 --prop pageBreakBefore=true\n```\n\n`--prop break=newPage` is an alias for `pageBreakBefore=true`. Preview with `view html` and check for blank pages.\n\n### Report-level recipes\n\nPatterns that come up on every long-form report. Each has been executed and `validate`-passed.\n\n**(a) Rich cover page — hit the ≥ 60% filled floor.** Stack a confidentiality banner, title, subtitle, client/project/date block, and a key-themes strip, then force the next section onto a new page:\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"CONFIDENTIAL — CLIENT USE ONLY\" --prop align=center --prop size=9pt --prop color=C00000 --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Strategic Growth Review\" --prop style=Title --prop size=32pt --prop bold=true --prop align=center --prop font=Cambria --prop spaceAfter=8pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"FY26 Outlook and Scenario Planning\" --prop italic=true --prop size=16pt --prop align=center --prop spaceAfter=36pt\nofficecli add \"$FILE\" /body --type paragraph --prop text='Prepared for: Acme Corp. Leadership Team' --prop align=center --prop size=11pt\nofficecli add \"$FILE\" /body --type paragraph --prop text='Engagement: 2026-04 — 2026-06' --prop align=center --prop size=11pt --prop spaceAfter=36pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Key themes: 1) margin resilience, 2) EMEA expansion, 3) capital allocation.\" --prop align=center --prop italic=true --prop size=10pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1 --prop pageBreakBefore=true\n```\n\n**(b) Page X of Y footer — composite PAGE + NUMPAGES.** Add the footer paragraph, then three child ops build `Page <X> of <Y>` live. The official `help docx footer` recipe.\n\n```bash\nofficecli add \"$FILE\" / --type footer --prop type=default --prop text=\"Page \" --prop align=center --prop size=9pt\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type field --prop fieldType=page\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type run --prop text=\" of \"\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type field --prop fieldType=numpages\nofficecli get \"$FILE\" \"/footer[1]/p[1]\" --depth 1 | grep -o fldChar | wc -l   # expect ≥ 4; use grep -o ... | wc -l, NOT grep -c (single-line XML returns 1)\n```\n\n**(c) Header row with fill and white bold text.** Order matters — populate header cell text FIRST (runs don't exist in empty cells; a `set …/tc[N]/p[1]/r[1]` on an empty cell errors \"No r found\"), THEN cell fill, THEN run formatting:\n\n```bash\nofficecli add \"$FILE\" /body --type table --prop rows=5 --prop cols=4 --prop width=100%\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop header=true --prop c1=Quarter --prop c2=Revenue --prop c3=Growth --prop c4=Status\nfor col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[$col]\" --prop fill=1F4E79\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[$col]/p[1]/r[1]\" --prop bold=true --prop color=FFFFFF\ndone\nfor row in 3 5; do for col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[$row]/tc[$col]\" --prop fill=D9E2F3      # zebra stripe\ndone; done\n```\n\n**(d) Financial table — right-align numbers, bold totals, bottom border on total row.**\n\n```bash\nfor row in 2 3 4 5; do for col in 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[$row]/tc[$col]/p[1]\" --prop align=right\ndone; done\nfor col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[5]/tc[$col]/p[1]/r[1]\" --prop bold=true\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[4]/tc[$col]/p[1]\" --prop pbdr.bottom=\"single;6;000000;0\"\ndone\n```\n\n**(e) Cell with multiple bullets (SWOT / risk matrix).** `c1=\"a\\nb\"` gives a `<w:br/>` line break within **one** paragraph — fine for plain multi-line text, but bullets need separate paragraphs. Seed the first via `set c1=`, then `add paragraph` (with `listStyle=bullet`) under the cell per subsequent bullet:\n\n```bash\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop c1=\"Installed base of 18k enterprise seats\"\nofficecli add \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]\" --type paragraph --prop text=\"Margin structure above peer median\" --prop listStyle=bullet\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[1]\" --prop listStyle=bullet\n```\n\nIf the seeded line lands at the bottom, re-order: `officecli move \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[N]\" --index 0`.\n\n**(f) TOC without a field engine.** A CLI-only pipeline cannot calculate reliable page numbers. Keep the live TOC, set `updateFields=true`, and tell the recipient to open the document in Word or another compatible field engine. Do not replace it with guessed static page numbers.\n\n### Template delivery — separating Template Notes from end-user content\n\nHR / legal / vendor templates carry internal-only guidance (\"replace `{{CompanyName}}`\") that must NOT ship. Two working patterns:\n\n- **Trailing \"Template Notes\" section** under a clear `Heading 1` (\"Template Notes for HR Users\") with all instructions below it; before distribution, `remove` from the heading downward (locate with `query 'paragraph[style=Heading1]:contains(\"Template Notes\")'`).\n- **Bookmark-bounded internal section** between `__template_notes_start` / `_end` bookmarks; at delivery `raw-set` removes everything between the anchors.\n\nDelivery gate for templates: after removal, `query 'p:contains(\"Template Notes\")'` AND `query 'p:contains(\"{{\")'` both return empty. If a notes paragraph survives, a downstream employee reads internal language.\n\n### Advanced / specialty topics (skip if you are writing a report)\n\nReports, memos, letters, proposals, and HR templates don't need this. Keep reading only if your document is academic (equations, footnotes, bibliography), reviewed (comments, tracked changes), or marked (watermark).\n\n**Equations and footnotes.** `--type equation` takes LaTeX — `\\frac`, `\\sum`, Greek, `\\mathit`, `\\mathcal` all render. By default it creates a standalone `/body/oMathPara[N]` display block; pass `--prop mode=inline` with a paragraph parent path (`add \"/body/p[N]\" --type equation --prop formula=… --prop mode=inline`) to drop an inline `<m:oMath>` into running text. Footnotes auto-number by paragraph index. Bibliography hanging indent: `firstLineIndent=-720 indent=720` per entry.\n\n```bash\nofficecli add \"$FILE\" /body --type equation --prop formula=\"\\\\frac{a}{b} + \\\\sum_{i=1}^{n} x_i\"\nofficecli add \"$FILE\" \"/body/p[3]\" --type footnote --prop text=\"See Appendix A for methodology.\"\n```\n\n**Comments and tracked changes.** Bulk accept/reject: `set \"$FILE\" /revision --prop revision.action=accept` (or `--prop revision.action=reject`); narrow with a selector like `/revision[@author=Alice]` or `/revision[@type=ins]`. Locate individual changes with `query ins` and `query del` (`trackedchange` is not a selector). Create tracked changes on a run with `--prop revision.type=ins|del --prop revision.author=…` (`help docx run` for the full `revision.*` set — `format`/`moveFrom`/`moveTo` too). Add a comment: `add \"/body/p[4]\" --type comment --prop author=… --prop text=…`; reply-thread it with `--prop parentId=N` and mark it resolved with `set \"/comments/comment[N]\" --prop done=true` (resolve rather than delete to keep the audit trail — `query 'comment[done=false]'` then lists what's still open). Prop schema: `help docx comment` / `help docx run`.\n\n**Watermark.** `add / --type watermark --prop text=\"DRAFT\" --prop color=BFBFBF --prop opacity=0.8` in one command (default opacity 0.5); `set /watermark --prop opacity=…` adjusts it later.\n\n**When to switch skills.** Stay in docx for chapter drafts, ≤ 3 footnotes, ≤ 2 equations, no bibliography/cross-refs. Switch to **`academic-paper`** for citation styles (APA / Chicago / IEEE / GB 7714), in-text↔reference auto-linking, numbered equations with `\\ref`, \"List of Figures\", or auto-updating cross-refs. Switch to **`officecli-word-form`** when the document's purpose is **data capture** — fillable forms, contracts with user-fill slots, questionnaires, mail-merge templates (`<w:sdt>` content controls, `<w:ffData>`, `documentProtection=forms`).\n\n### Raw-set escape hatch (L1 / L2 / L3)\n\nThree tiers of precision; use the lowest that does the job.\n\n- **L1 — high-level props** (`--prop text=…`, `--prop style=Heading1`): your default. Covers 80%.\n- **L2 — dotted-attr fallback** (`pbdr.top=`, `ind.left=`, `shd.fill=`, `padding.top=`, `font.size=`): when L1 lacks the knob. Example: `--prop pbdr.bottom=\"single;6;1F4E79;0\"`. Emits schema-valid XML.\n- **L3 — `raw-set` with XML**: last resort, no schema protection. Use for internal hyperlinks, composite fields, and other shapes the typed verbs can't express (see XML appendix).\n\nBorders use the format `style;size;color;space`: `single;4;FF0000;1`. Hex colors never start with `#`: `FF0000`. Scheme color names (`accent1..6`, `dark1`/`dark2`, `light1`/`light2`, `hyperlink`) are accepted anywhere a hex color is — prefer hex for stable colors across themes.\n\n## QA (Required)\n\n**Assume there are problems — QA is a bug hunt, not a confirmation step.** Your first document is almost never correct; zero issues on first inspection means you weren't looking hard enough. Headings look fine until `view outline` shows an H3 directly under an H1; the footer shows \"Page 1\" until `get --depth 3` reveals a static run, not a field.\n\n### Minimum cycle before \"done\"\n\n1. `officecli view \"$FILE\" issues` — empty paras, missing alt text, formatting anomalies.\n2. `officecli view \"$FILE\" outline` — heading hierarchy (no H1 → H3 skips), TOC presence, section count.\n3. `officecli view \"$FILE\" text --max-lines 400` — typos, stray `\\$`/`\\t`/`\\n` literals, placeholder tokens.\n4. `officecli validate \"$FILE\"` — schema check (the Delivery Gate re-runs this on the closed, on-disk file).\n5. **Visual pass — whole document as a contact sheet** (vision-capable agents only — if you cannot interpret images, skip this step: steps 1–4 are your ceiling, and flag the document \"not visually verified\" at handoff). `officecli view \"$FILE\" screenshot --grid auto -o /tmp/sheet.png`, then Read it. `--grid auto` tiles **every page** into one image (auto column count; `--grid 4` to force) — you *see* pagination, blank pages, heading rhythm, lopsided margins, and TOC/cover placement, not just the DOM. Windows+Word renders each page through real Word; elsewhere HTML. If the screenshot fails, fall back to `view html` and flag cross-page breaks / alignment / rhythm as \"not visually verified\". Thumbnails only **locate**: confirm any fine call (column alignment, line spacing, indents, dark-on-dark, caption placement) on the suspect page at full resolution with `screenshot --page N` (no `--grid`; real Word on Windows). \"validate pass\" is not delivery; \"looks like a real document\" is.\n6. If anything failed, fix, then **rerun the full cycle** — one fix commonly creates another problem.\n\n### Delivery Gate (run before handing off — any failure = REJECT, do NOT deliver)\n\nCopy-paste, set `FILE`, and refuse to declare done until every gate prints OK.\n\n```bash\nFILE=\"your-file.docx\"\n\n# Gate 1 — schema.\nofficecli close \"$FILE\" 2>/dev/null\nofficecli validate \"$FILE\" | grep -q \"no errors found\" || { echo \"REJECT Gate 1: validate failed\"; exit 1; }\necho \"Gate 1 OK\"\n\n# Gate 2 — token leak (shell-escape / template tokens / literal \\$ \\t \\n). grep -c never false-PASSes.\nLEAK=$(officecli view \"$FILE\" text | grep -cE '(\\$[A-Za-z_]+\\$|\\{\\{[^}]+\\}\\}|<TODO>|xxxx|lorem|\\\\[\\$tn])')\n[ \"$LEAK\" -eq 0 ] && echo \"Gate 2 OK\" || { echo \"REJECT Gate 2: $LEAK leak line(s)\"; officecli view \"$FILE\" text | grep -nE '(\\$[A-Za-z_]+\\$|\\{\\{[^}]+\\}\\}|<TODO>|xxxx|lorem|\\\\[\\$tn])'; exit 1; }\n# A TOC placeholder is valid before a Word-compatible field engine updates it; confirm the TOC field and updateFields setting structurally instead.\n\n# Gate 3 — live PAGE field exists when a footer is expected.\nFLD=$(officecli query \"$FILE\" 'field[fieldType=page]' --json | jq '.data.results | length')\n[ \"$FLD\" -ge 1 ] && echo \"Gate 3 OK\" || { echo \"REJECT Gate 3: no live PAGE field\"; exit 1; }\necho \"Delivery Gate PASS\"\n```\n\n### Field / cached-value spot-check\n\nFields carry cached values that may be stale or empty at write time — confirm existence by **structure, not text**.\n\n- **Footer PAGE:** `get /footer[N] --depth 3` lists the begin / instrText / separate / cached / end run chain — ≥ 5 runs for one PAGE, ≥ 11 for composite \"Page X of Y\". A single run with text `\"Page\"` = field missing; re-add with `--prop field=page`.\n- **TOC:** `get /toc[1] --depth 2` shows field structure. Page numbers may read `1 1 1 1` or `Update field to see…` until recalculated (see §Table of Contents — set `updateFields=true`).\n- **MERGEFIELD:** `query 'field[fieldType=mergefield]'` — one per slot, no literal `{{name}}` elsewhere.\n\n### Honest limit\n\n`validate` catches schema errors, not design errors — a document can pass it with wrong heading hierarchy, fake-Heading-1 sizes, placeholder tokens as body text, or an empty first-page footer on a coverless document. The contact-sheet visual pass (`screenshot --grid`) and the field-structure check are how you catch what validation can't.\n\n### QA display notes (don't chase these)\n\n- `view text` shows `\"1.\"` for every numbered list item regardless of rendered number — actual output increments correctly.\n- `view issues` flags \"body paragraph missing first-line indent\" on cover paragraphs, centered headings, list items, bibliography entries — first-line indent is only required for APA/academic body text; on block-style professional documents these are expected.\n\n## Known Issues & Pitfalls\n\nWhen something \"looks broken\", attribute it before chasing: **[AGENT-ERROR]** the document is wrong (fix it) · **[RENDERER-BUG]** the document is correct, a viewer renders it differently (don't chase) · **[SKILL gap]** the skill didn't teach the rule (file an issue).\n\n### Renderer quirks (cross-viewer, [RENDERER-BUG] — don't chase)\n\nBefore calling a color/field/chart broken, open the file in the user's target viewer; if it looks correct there it's a viewer quirk.\n\n- **PAGE field may render literal \"Page\"** (no number) until recalculated — judge by `fldChar` presence, not the digit.\n- **TOC cached page numbers may read \"1 1 1 1\"** until F9.\n- **Pie / doughnut fill may collapse to one color** in some viewers (column/bar render fine).\n- **Form-control checkboxes may render double-boxed**; **OMML equation baselines** may shift across viewers (XML identical).\n\n### Common pitfalls\n\n| Pitfall | Correct approach |\n|---|---|\n| `--index` vs `[N]` | `--index` is 0-based; `[N]` paths are 1-based |\n| Multiple `add --index N` with the same N | Each insert shifts later content down; reusing N puts later items BEFORE earlier ones — insert in reverse order, or `move --after/--before` anchored on `paraId` |\n| Unquoted `[N]` in zsh/bash | Quote every path: `\"/body/p[1]\"` |\n| `[last]` as predicate | Must be `[last()]` with parens |\n| Raw twips in spacing | Use unit-qualified values: `12pt`, `0.5cm`, `1.5x` |\n| Empty paragraphs for spacing | Use `spaceBefore` / `spaceAfter` |\n| Row-level `set` for cell formatting | Row `set` only supports `height`, `header`, `c1..cN` text; format goes on the cell paragraph / run |\n| `listStyle` on a run | It's a paragraph property |\n| Indent via leading spaces | `indent=720` / `firstLineIndent=360` / `hangingIndent=720` (dotted `ind.left` / `ind.firstLine` also work) |\n| Cover page-number suppression via `set differentFirstPage=true` | UNSUPPORTED — add a first-type footer: `--type footer --prop type=first --prop text=\"\"` |\n| Need a fresh chapter page | Use `pageBreakBefore=true` on the heading; use an explicit `pagebreak` only as an alternative, never both |\n| Multiple bullet paragraphs in one cell | `c1=\"a\\nb\"` makes a `<w:br/>` line break (one paragraph); for separate bullet paragraphs use recipe (e) |\n| `raw-set` when dotted-attr would work | Prefer L2 dotted-attr over L3 raw-set |\n| Next paragraph inherits the previous Heading style | Set explicit `--prop style=Normal` on the following paragraph |\n| Modifying a file open in Word | Close it in Word first |\n| Echo into batch breaks on `$`/`'` | Heredoc with single-quoted delimiter: `cat <<'EOF' \\| officecli batch …` |\n\n## Raw-set XML appendix (L3 patterns)\n\n`raw-set` injects literal OOXML — no schema protection. Element order in `<w:pPr>`: `pStyle`, `numPr`, `spacing`, `ind`, `jc`, `rPr` (last). Smart quotes as entities (`&#x2018;`/`&#x2019;`/`&#x201C;`/`&#x201D;`). Add `xml:space=\"preserve\"` to any `<w:t>` with leading/trailing spaces. RSIDs are 8-digit hex. Use \"Claude\" as the author for tracked changes/comments unless the user names another.\n\n**Tracked-change insertion / deletion** — prefer the high-level `--prop revision.type=ins|del` on a run; raw-set only for what the typed path can't express (rejecting/restoring another author's change, below). Replace the whole `<w:r>…</w:r>`, never inject tags inside a run; copy the original `<w:rPr>` into both to preserve formatting. Inside `<w:del>` use `<w:delText>` (and `<w:delInstrText>` for instructions):\n\n```xml\n<w:r><w:t>The term is </w:t></w:r>\n<w:del w:id=\"1\" w:author=\"Claude\" w:date=\"2026-01-01T00:00:00Z\"><w:r><w:delText>30</w:delText></w:r></w:del>\n<w:ins w:id=\"2\" w:author=\"Claude\" w:date=\"2026-01-01T00:00:00Z\"><w:r><w:t>60</w:t></w:r></w:ins>\n<w:r><w:t> days.</w:t></w:r>\n```\n\nWhen deleting ALL content of a paragraph/list item, also mark the paragraph mark deleted (`<w:del/>` inside `<w:pPr><w:rPr>`) — otherwise accepting changes leaves an empty paragraph. To **reject another author's insertion**, nest your `<w:del>` inside their `<w:ins>`; to **restore their deletion**, add a `<w:ins>` after it (don't modify theirs).\n\n**Internal hyperlink to a bookmark** (prefer the high-level `--prop anchor=` path; raw-set only for custom run styling the command can't express):\n\n```xml\n<w:hyperlink w:anchor=\"chapter1\"><w:r><w:rPr><w:rStyle w:val=\"Hyperlink\"/></w:rPr><w:t>See Chapter 1</w:t></w:r></w:hyperlink>\n```\n\n**Composite field in one run** (e.g. two fields the single-command path can't compose) — the `fldChar begin / instrText / separate / value / end` chain:\n\n```xml\n<w:r><w:fldChar w:fldCharType=\"begin\"/></w:r>\n<w:r><w:instrText xml:space=\"preserve\"> PAGE </w:instrText></w:r>\n<w:r><w:fldChar w:fldCharType=\"separate\"/></w:r>\n<w:r><w:t>1</w:t></w:r>\n<w:r><w:fldChar w:fldCharType=\"end\"/></w:r>\n```\n\n**Comment markers** are siblings of `<w:r>`, NEVER inside one (reply threading and resolved-state are high-level — `--prop parentId=`/`done=`, see Comments above):\n\n```xml\n<w:commentRangeStart w:id=\"0\"/><w:r><w:t>annotated text</w:t></w:r><w:commentRangeEnd w:id=\"0\"/>\n<w:r><w:rPr><w:rStyle w:val=\"CommentReference\"/></w:rPr><w:commentReference w:id=\"0\"/></w:r>\n```\n\nForce field recalc on open with `officecli set \"$FILE\" /settings --prop updateFields=true` (writes `<w:updateFields w:val=\"true\"/>`; covers the layout-dependent fields PAGE / PAGEREF / NUMPAGES / TOC page numbers — no raw-set needed). For SEQ numbering, prefer `set / --prop recalcFields=seq`, which writes correct cached values now without waiting on Word.\n\n### Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <verb> <element>`, `--json` for agents. Help is the authoritative schema; this skill is the decision guide.\n","skills/officecli-academic-paper/SKILL.md":"---\nname: officecli-academic-paper\ndescription: \"Use this skill to build academic-style .docx output: journal / conference / thesis chapters carrying formal citation style (APA, Chicago, IEEE, MLA), numbered equations, figure & table cross-references, footnotes/endnotes, bibliography, or multi-column journal layout. Trigger on: 'research paper', 'journal paper', 'conference paper', 'manuscript', 'thesis', 'APA', 'MLA', 'Chicago', 'IEEE two-column', 'bibliography', 'hanging indent', 'citation style', 'abstract + keywords', 'equation numbering', 'cross-reference', paper with footnotes/endnotes. Output is a single .docx.\"\n---\n\n# OfficeCLI Academic Paper Skill\n\n**This skill is a scene layer on top of `officecli-docx`.** Every docx hard rule — style architecture, heading hierarchy, shell quoting, page-break rules, live PAGE field, Delivery Gate, renderer quirks — is inherited, not re-taught. This file adds only what academic papers need on top: citation styles, equations, SEQ / PAGEREF cross-refs, multi-column journal layout, bibliography hanging indent, abstract/keywords/affiliation block.\n\nWhen the docx base rules cover it, the text here says `→ see docx v2 §X`. Read docx v2 first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what an academic paper requires, not every command flag.** When a prop name, enum value, or field instruction is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help docx                          # All docx elements\nofficecli help docx <element>                # Full schema (e.g. section, equation, field, footnote)\nofficecli help docx <element> --json         # Machine-readable\n```\n\nHelp is pinned to the installed CLI version. **When this skill and help disagree, help wins.** Every `--prop X=` in this file has been grep-verified against `officecli help docx <element>` — if help adds / renames a prop in a later version, trust help.\n\n## Mental Model & Inheritance\n\n**Inherits docx v2.** You should have read `skills/officecli-docx/SKILL.md` first. This skill assumes you know how to add paragraphs, set styles, build tables, insert images, manage TOC/footer/headers, force page breaks, and run the Delivery Gate. If any of those are unfamiliar, open a second session on docx v2 before continuing.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see docx v2 §Shell & Execution Discipline. The same rules apply here verbatim — quote `[N]` paths, single-quote any value containing `$` (including `$2.8B` in a body paragraph or `@` DOIs), never hand-write `\\$ \\t \\n` in executable examples, one command at a time. Academic-paper examples below use `$FILE` as a shell variable (`FILE=\"thesis.docx\"`).\n\n## What \"academic\" means here (identity)\n\nAn academic paper is a docx with a **scholarly layer** on top: verifiable citations, precise equations, cross-refs that stay in sync, a formatted reference list. The base docx rules still apply; academic adds six deltas:\n\n1. **Citation style is a contract.** APA / Chicago / IEEE / MLA each dictate author format, date placement, reference-list order, in-text marker shape. Pick one at the start; every later decision (hanging indent, footnote vs parenthetical, `[1]` vs `(Smith, 2024)`) follows.\n2. **Equations are first-class content** — inline `oMath` inside prose, display `oMathPara` as standalone blocks, optionally numbered.\n3. **Figures and tables auto-number.** `SEQ Figure` / `SEQ Table` fields count them; `PAGEREF` links \"see Fig. 2\" to its live page number.\n4. **Bibliography uses hanging indent** (first line flush left, continuation lines indented). Not first-line indent. Not left indent alone. Hanging.\n5. **Abstract / keywords / affiliation block** is a first-page three-piece, not a cover in the marketing sense. Block-style abstract, no first-line indent, no decoration.\n6. **Multi-column layout** appears in IEEE / ACM / Nature / many journals: single-column abstract + two-column body.\n\n### Reverse handoff — when to go BACK to docx\n\nStay in **docx v2** for white papers, policy briefs, technical reports, HR templates — anything without a venue / citation style. Use **this skill** only when the document will carry at least TWO of: citation-style biblio, equations, SEQ/PAGEREF cross-refs, multi-column, abstract + keywords block.\n\n## Workflow — 5 verbs\n\n1. **Read the venue spec.** APA 7 / Chicago 17 / IEEE / MLA 9 / journal-specific. Line spacing, font, citation shape, biblio sort order — everything downstream follows from this one decision.\n2. **Plan the sections.** Abstract → keywords → introduction → methods → results → discussion → conclusion → references. Estimate heading count for TOC decision (3+ headings = add a TOC, see docx v2 §Table of Contents).\n3. **Set styles up front.** Heading1 / Heading2 / Heading3 / Caption / AbstractTitle / Bibliography. Define all styles BEFORE any content (→ see docx v2 §Paragraphs and styles — same rule here, same failure mode if skipped).\n4. **Build body in order.** Cover / title block → abstract → keywords → TOC (if needed) → body sections in reading order → figures / tables with SEQ captions → bibliography → footnotes are added last by paragraph path.\n5. **QA — Delivery Gate.** Inherit docx v2 Gates 1-3, then add academic Gates 4-5 below.\n\n## Requirements (academic floor on top of docx v2)\n\nEverything in docx v2 §Requirements for Outputs applies. On top of that, academic papers MUST meet these additional rules:\n\n### Typography and spacing (venue-aware)\n\n- **Font.** Times New Roman 11-12pt body (default) or venue-specified (IEEE uses Times 10pt 2-col; APA allows Calibri 11pt). Same body font throughout; no decorative heading fonts.\n- **Heading hierarchy.** H1 = 20pt bold, H2 = 14pt bold, H3 = 12pt bold italic, body = 11-12pt. (Same numbers as docx v2 — restated because academic papers never rely on Word defaults.)\n- **Line spacing.** APA 7 = 2x (double). Chicago / IEEE / most journals = 1.5x. Never below 1.15x. Set on body paragraphs and on References.\n- **Margins.** 1 inch (1440 twips) all sides unless the venue says otherwise (some journals require 1.25in left for binding — check the spec).\n\n### Abstract, bibliography, caption placement\n\n- **Abstract is block-style.** NO `firstLineIndent`. Use `spaceAfter=12pt` for paragraph separation. If `view issues` reports \"body paragraph missing first-line indent\" on an Abstract paragraph, it's a false positive — ignore.\n- **Bibliography uses hanging indent.** Each entry is one paragraph with `indent=720 hangingIndent=720` (left indent 0.5\", first-line reversed by same amount). First line flush left; wraps indent under author name.\n- **Figure captions go BELOW the figure.** Table captions go ABOVE the table. This is the single rule most non-academics get wrong — APA, Chicago, IEEE, MLA all agree on it.\n- **Citation round-trip.** Every in-text citation key must resolve to an entry in the reference list. Delivery Gate 4 verifies.\n- **SEQ presence.** Any paper with numbered figures or tables must carry live `SEQ Figure` / `SEQ Table` fields (not hardcoded \"Figure 1\" text that drifts when you insert a new figure mid-document). Delivery Gate 5 verifies.\n\n### Cover / first-page block\n\nAcademic covers differ from professional covers. Minimum elements: title (centered, 20-22pt bold), author(s), affiliation, submission target or journal, date, abstract, keywords. The \"60% fill\" rule from docx v2 §Visual delivery floor still applies — a three-line cover with half a page of whitespace is a fail. See §Abstract / keywords / affiliation block below for the first-page recipe.\n\n### Section numbering convention (STYLE-DEPENDENT — do not apply blindly)\n\nAcademic section numbers are **part of the heading text**, not computed via list numbering. `officecli`'s `numId`/`listStyle` mechanism is fragile across Heading1 re-use, so hand-write the prefix. BUT the prefix shape varies by style — DO NOT use the same form for all four:\n\n| Style | H1 format | H2 format | Example |\n|---|---|---|---|\n| **APA 7** | **UNNUMBERED centered bold** | Unnumbered left-aligned bold | `Introduction` / `Methods` (centered) |\n| **Chicago** | `\"N. Title\"` left-aligned | `\"N.M Title\"` | `1. Introduction`, `2.1 Policy Formation` |\n| **IEEE** | `\"N. TITLE\"` ALL CAPS + Roman numerals | `A. Subtitle` title case | `I. INTRODUCTION`, `II. RELATED WORK`, `A. Datasets` |\n| **MLA 9** | Unnumbered left-aligned bold | Same | `Literature Review` (no prefix) |\n\nAPA 7 L1 headings are **centered, bold, unnumbered**; L2 are flush-left bold; L3 flush-left bold italic; L4/L5 run-in. Do NOT prefix APA headings with `1. / 2.` — that is Chicago/IEEE convention. IEEE wants ALL CAPS with Roman numerals (`I. INTRODUCTION`); inside each section, use `A./B./C.` sub-headings (title case). Arabic-numbered body sections are Chicago-style only.\n\n**Exception for all four**: References / Bibliography / Works Cited / Acknowledgments are unnumbered regardless of style — omit the `N.` prefix.\n\n## Quick Start — minimal APA paper\n\n```bash\nFILE=\"paper.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" / --prop defaultFont=\"Times New Roman\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote Work and Team Cohesion\" --prop align=center --prop size=20pt --prop bold=true --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen\" --prop align=center --prop size=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Department of Psychology, Stanford University\" --prop align=center --prop size=11pt --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Abstract\" --prop align=center --prop size=14pt --prop bold=true --prop spaceBefore=12pt --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"This study examines remote-work adoption on team cohesion across 18 months...\" --prop size=12pt --prop lineSpacing=2x --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Keywords: remote work, team cohesion, psychological safety\" --prop italic=true --prop size=11pt --prop spaceAfter=18pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"1. Introduction\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote-work research (Smith, 2024) has expanded since 2020...\" --prop size=12pt --prop lineSpacing=2x --prop firstLineIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"References\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Smith, J. (2024). Remote work and cohesion. Journal of Applied Psychology, 109(3), 412-430.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=10pt --prop field=page\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nTen-line skeleton. Real papers grow by adding more body paragraphs, more bibliography entries (each with the same `indent=720 hangingIndent=720` pair), figures / tables with captions, and a TOC if there are 3+ Heading1s. The Quick Start validates clean; the sections below elaborate each dimension.\n\n## Citation style recipes\n\nFour mainstream families. Pick one at project start; every downstream decision follows. **Per-style decision table:**\n\n| Style | In-text shape | Reference list order | Body line spacing | Footnotes? |\n|---|---|---|---|---|\n| APA 7 | `(Smith, 2024)` or `Smith (2024)` | Alphabetical by author | 2x (double) | Rare (content notes only) |\n| Chicago 17 (Notes-Bib) | Superscript footnote number | Alphabetical by author | 1.5x-2x | **Primary** (full citation in footnote) |\n| IEEE | `[1]`, `[2]`, ..., `[N]` | Order of first citation | 1.15x-1.5x, 2-col | Rare |\n| MLA 9 | `(Smith 412)` page-number | Alphabetical by author, \"Works Cited\" | 2x | Rare |\n\nShared defaults across all four: reference-list paragraphs use `indent=720 hangingIndent=720` (hanging indent 0.5\"); add a live TOC if 3+ Heading1s (→ see docx v2 §Table of Contents); set `updateFields=true` and report TOC page numbers as uncomputed until a Word-compatible field engine updates them.\n\n### APA 7 (social sciences — psychology, education, management)\n\n- In-text: `(Author, Year)` or `Author (Year)` for narrative. Page number required on direct quotes: `(Smith, 2024, p. 15)`. Three+ authors: `(Smith et al., 2024)` after first citation.\n- Reference list order: **alphabetical by first author's surname**. Title caps: sentence case for article titles, title case for journal names (italic).\n- Reference shape: `Author, A. A., & Co-Author, B. B. (Year). Title of article. Journal Name, Volume(Issue), pages.` DOI preferred over URL; present as https URL, not `doi:` prefix.\n- Double-space everything (`lineSpacing=2x`) including abstract and references. Body first-line indent = 0.5\" (`firstLineIndent=720`).\n\n```bash\n# Body paragraph with parenthetical citation\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote work adoption accelerated during the pandemic (Kramer & Kramer, 2020).\" --prop size=12pt --prop lineSpacing=2x --prop firstLineIndent=720\n# Reference entry with hanging indent\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Kramer, A., & Kramer, K. Z. (2020). The potential impact of the Covid-19 pandemic on occupational status. Journal of Vocational Behavior, 119, 103442.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\n# DOI hyperlink appended to the reference paragraph\nofficecli add \"$FILE\" \"/body/p[last()]\" --type hyperlink --prop url=\"https://doi.org/10.1016/j.jvb.2020.103442\" --prop text=\"https://doi.org/10.1016/j.jvb.2020.103442\"\n```\n\nQA: `officecli query \"$FILE\" 'paragraph[hangingIndent]'` returns every reference entry; zero references with first-line indent instead of hanging.\n\n### Chicago 17 — Notes-Bibliography (humanities — history, philosophy, religion)\n\n- In-text: superscript footnote number; full citation in the first footnote (`Timothy Brook, The Troubled Empire (Cambridge, MA: Harvard UP, 2010), 142.`); **shortened form** thereafter (`Brook, Troubled Empire, 150.`).\n- **Repeat-citation rule (Chicago 17, op. cit. deprecated):**\n  - **Immediately-consecutive** citation of **the same source, same page** → `Ibid.`\n  - **Immediately-consecutive, different page** of same source → `Ibid., 22.`\n  - Non-consecutive repeat → **shortened form** (`Brook, Troubled Empire, 150.`), NOT `op. cit.`. Chicago 17 drops `op. cit.` — use shortened form every time except for immediate repeats.\n- Bibliography at end, **alphabetical by first author's surname** (\"Brook, Timothy.\"), hanging indent. Footnote body renders at the viewer's footnote default (typically 10pt); bibliography entries 12pt. (The `footnote` element exposes only `text` — size is not settable per-footnote; trust renderer defaults.)\n- Typical split for primary-source-heavy papers: `Primary Sources` and `Secondary Sources` as two Heading2s under a single `Bibliography` Heading1. Book titles italic in both footnotes and bibliography.\n- Chicago also has an Author-Date variant used in the sciences — if the venue specifies Chicago Author-Date, fall back to the APA recipe and change only the punctuation (no comma between author and year: `(Smith 2024)`).\n\n```bash\n# Body paragraph that will anchor a footnote, then the footnote itself\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"The Ming dynasty's 海禁 policy shaped coastal trade for two centuries.\" --prop size=12pt --prop lineSpacing=1.5x --prop firstLineIndent=720\nofficecli add \"$FILE\" \"/body/p[last()]\" --type footnote --prop text=\"Timothy Brook, The Troubled Empire: China in the Yuan and Ming Dynasties (Cambridge, MA: Harvard University Press, 2010), 142.\"\n# Next footnote — shortened form\nofficecli add \"$FILE\" \"/body/p[last()]\" --type footnote --prop text=\"Brook, Troubled Empire, 150.\"\n# Bibliography section split — primary sources first\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Bibliography\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Primary Sources\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Ming Shilu 明實錄. Taipei: Academia Sinica, 1966.\" --prop size=12pt --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Secondary Sources\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Brook, Timothy. The Troubled Empire: China in the Yuan and Ming Dynasties. Cambridge, MA: Harvard University Press, 2010.\" --prop size=12pt --prop indent=720 --prop hangingIndent=720\n```\n\nQA: `officecli query \"$FILE\" 'footnote'` count ≥ body-paragraph citation count.\n\n### IEEE (engineering — transactions, conference proceedings)\n\n- In-text: `[1]`, `[2]`. Numbered in **order of first appearance**, not alphabetical. Reuse the same number for repeat citations. `[1, p. 15]` for page refs, `[1]-[3]` for a range.\n- Reference entry starts with the bracketed number: `[1] A. Smith and B. Jones, \"Title,\" IEEE Trans. X, vol. 5, no. 3, pp. 1-10, 2024, doi: ...`. Authors are initial-first; journal names abbreviated per IEEE list (`IEEE Trans. Neural Netw.`, not full name).\n- Body is **two-column** (see §Multi-column below). Abstract is single-column above the fold, 10pt, 1.15x line spacing, typically 200-250 words.\n- First-line indent on body paragraphs = 0.2\" (`firstLineIndent=288` twips ≈ 14pt). Smaller than APA's 0.5\" because the 2-col width is narrower.\n- **Section headings: ALL CAPS with Roman numerals** — `I. INTRODUCTION`, `II. RELATED WORK`, `III. METHOD`. Sub-sections `A. Datasets`, `B. Baselines` in title case. Do NOT use `1. Introduction` (Arabic) for IEEE — that is Chicago style.\n- **Tables are numbered Roman**: `Table I`, `Table II`, `Table III`. Figures remain Arabic (`Fig. 1`, `Fig. 2`). `recalcFields=seq` writes Arabic cached values for both — for IEEE Roman tables, either patch the cached `<w:t>` to Roman manually after recalc, or accept Arabic and note it in the cover letter.\n\n```bash\n# Body citing reference 1\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Attention-based anomaly detection has been applied to industrial sensor data [1], [2].\" --prop size=10pt --prop lineSpacing=1.15x\n# Reference list entry — number in the text\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"[1] A. Smith and B. Jones, \\\"Attention for anomaly detection,\\\" IEEE Trans. Neural Netw., vol. 35, no. 2, pp. 412-430, 2024.\" --prop size=10pt --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"[2] C. Lee, \\\"Time-series anomaly survey,\\\" in Proc. ICML, 2023, pp. 1200-1215.\" --prop size=10pt --prop indent=720 --prop hangingIndent=720\n```\n\nQA: the highest `[N]` in body must equal the number of reference-list entries. Grep: `officecli view \"$FILE\" text | grep -oE '\\[[0-9]+\\]' | sort -u | tail -5`.\n\n### MLA 9 (literature, languages, cultural studies)\n\nDiff vs APA: in-text is `(Author Page)` **no comma** (e.g. `(Smith 412)`); direct quotes always carry the page number. Reference section titled **Works Cited** (not References / Bibliography). Entries alphabetical by surname, hanging indent, 2x spacing, nine \"core elements\" separated by periods: `Author. Title. Container, Other Contributors, Version, Number, Publisher, Date, Location.` — skip any that don't apply. Book titles italic; article titles in quotes. Otherwise identical to APA paragraph setup.\n\n## Equations (OMML — inline vs display)\n\n`--type equation` parses a LaTeX-ish formula into OMML. Two modes, selected by `--prop mode=`:\n\n| Mode | XML | Visual | Use |\n|---|---|---|---|\n| `display` (default) | `<m:oMathPara>` at `/body` | Standalone centered block | Numbered equations, theorem statements |\n| `inline` | `<m:oMath>` appended to a run inside a paragraph | Runs with the text | `if $x > 0$` style in prose |\n\n```bash\n# Display equation (own paragraph, centered) — explicitly set mode=display for clarity\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"x^2 + y^2 = z^2\"\n# Display equation with Greek / subscript / integral — verify rendering below\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"\\\\lambda_1 + \\\\alpha\"\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"\\\\frac{1}{2\\\\pi} \\\\int_0^{\\\\infty} e^{-x^2} dx\"\n# Inline equation INSIDE prose — required whenever variables like x_{t+1}, \\lambda, etc. appear in a body paragraph:\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Given the weight \" --prop size=11pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type equation --prop mode=inline --prop formula=\"W_t\"\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\" we define the loss...\"\n```\n\n**Verify equations render as OMML math**, not plain-text LaTeX tokens. After `close`, run:\n```bash\nofficecli view \"$FILE\" text | head -20       # λ₁ + α, ∫₀∞, x² must appear as unicode math (verified renders)\nofficecli raw \"$FILE\" /document | grep -c '<m:oMathPara'   # ≥ 1 per display equation\n```\nIf the body prose contains raw `lambda_1`, `x_{t+1}`, `\\alpha` or similar plain-text tokens (i.e., you typed them into a `paragraph --prop text=` instead of wrapping with `--type equation --prop mode=inline`), downstream viewers will render them as literal ASCII. **Rule: every mathematical variable / Greek letter / subscript in prose goes through `--type equation mode=inline`, never through `paragraph --prop text=`.**\n\n**LaTeX subset pitfalls** (non-negotiable):\n\n1. `\\left(...\\right)` / `\\left[...\\right]` with a sub/superscript **inside** the delimiters → parse error (`Error: cast object … Subscript`). An OUTER script (`\\left(x+y\\right)^2`) is fine; plain `(`, `)`, `[`, `]` always work and OMML auto-sizes them in display mode.\n2. `move` on `/body/oMathPara[N]` reorders the display equation (it repositions the wrapping paragraph). `--before <path>` may leave a stray empty paragraph; prefer `--index` or `--after` for a clean reorder.\n\n**Equation numbering** — no native `\\eqno`. The journal-standard layout is **one line**: equation centered, number flush-right at the column edge (`Y = A(X) ⊗ X      (1)`). Build it with two paragraph **tab stops** — a `center` tab at the column mid-point and a `right` tab at the column right edge — then lay out `[tab] equation(inline) [tab] (1)` in a single paragraph. Do NOT use a centered display equation followed by a separate right-aligned `(1)` line — that splits the number onto its own line and is the most common reason agents fail to reproduce the expected look.\n\n```bash\n# Tab positions depend on the COLUMN width (twips). Default blank doc = A4, 3.18cm margins\n#   → text width = 8300 twips.\n#   single column: center tab = 4150, right tab = 8300\n#   two columns (default 720-twip gutter): col = (8300-720)/2 = 3790 → center 1895, right 3790\n# (other page size / margins: text width = pageWidth - marginLeft - marginRight; recompute.)\nofficecli add \"$FILE\" /body --type paragraph                                   # the equation paragraph (say it lands at p[N])\nofficecli add \"$FILE\" \"/body/p[N]\" --type tab --prop pos=4150 --prop val=center\nofficecli add \"$FILE\" \"/body/p[N]\" --type tab --prop pos=8300 --prop val=right\nofficecli add \"$FILE\" \"/body/p[N]\" --type run --prop text=$'\\t'                 # tab → jump to center\nofficecli add \"$FILE\" \"/body/p[N]\" --type equation --prop mode=inline --prop formula='Y = A(X) \\otimes X'\nofficecli add \"$FILE\" \"/body/p[N]\" --type run --prop text=$'\\t(1)'             # tab → jump to right edge, then the number\n```\n\nFor a two-column section just use the two-column tab positions (1895 / 3790); the same paragraph then centers the equation within its column with `(1)` at the column's right edge. Schema: `officecli help docx tab`.\n\n**Do NOT place `--type equation` directly on a table cell `tc[N]` path** — it is rejected with a guard error (`table cells only accept paragraphs, tables, or SDTs`), so no bad XML is written. Target `tc[N]/p[1]` with `mode=inline` if you need equations in cells.\n\nFull equation schema: `officecli help docx equation`.\n\n## Figures, tables, and cross-references (SEQ + PAGEREF)\n\nTwo primitives, both **native fieldTypes** (`officecli help docx field` for the enum): `seq` for auto-numbered caption counters, `pageref` for \"see Fig. 2 on page 7\" back-references. Native fields insert with an unevaluated cached result; a single `set \"$FILE\" / --prop recalcFields=seq` (see §SEQ numbering below) fills the real numbers in document order — no per-field patching.\n\n### SEQ auto-numbering — figures and tables\n\nA SEQ field is a counter with a name (`identifier`). Every `SEQ Figure` increments the Figure counter on **recalc**; every `SEQ Table` increments the Table counter.\n\n**SEQ cached values — one command, after all captions are added.** A freshly-added SEQ field has no evaluated cached number; `view text` shows the `#OCLI_NOTEVAL!{SEQ Figure}` sentinel (and `Format[\"evaluated\"]=false`) until you recalc. **Do NOT patch each field by hand.** Once every figure/table caption is in place, run once:\n```bash\nofficecli set \"$FILE\" / --prop recalcFields=seq\n```\nIt counts `SEQ Figure` / `SEQ Table` fields in body document order and writes the real cached values (`Figure 1 / Figure 2 / Figure 3`), flipping `evaluated` true. Re-run it after inserting a caption mid-document so the numbers stay in sync. (Heading-relative `\\s` and SEQ inside headers/footers defer to Word's own recompute — see `help docx document`.) Verify: `officecli view \"$FILE\" text` shows distinct ascending numbers.\n\n```bash\n# Figure with caption BELOW the image. Caption = \"Figure <seq>: title\" + optional bookmark for cross-ref.\nofficecli add \"$FILE\" /body --type picture --prop src=arch.png --prop width=5in\nofficecli set \"$FILE\" \"/body/p[last()]/r[last()]\" --prop alt=\"Model architecture: attention over time-series sensors\"\n# Caption paragraph (below the figure, per academic convention)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Figure \" --prop style=Caption --prop size=10pt --prop italic=true --prop align=center\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=seq --prop identifier=Figure\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\": Attention-based anomaly detection model.\"\n# Bookmark the caption so other paragraphs can PAGEREF it\nofficecli add \"$FILE\" /body --type bookmark --prop name=fig_arch\n# (Numbers are filled later by a single `set / --prop recalcFields=seq`, after all captions exist.)\n```\n\n### PAGEREF — cross-reference by bookmark\n\n```bash\n# Cross-ref paragraph: \"see Figure 1 on page X\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"As shown in Figure 1 (see page \" --prop size=11pt --prop lineSpacing=1.5x\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=pageref --prop name=fig_arch\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\").\"\n```\n\n### Tables — caption ABOVE\n\n```bash\n# Caption first (ABOVE the table), THEN the table\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Table \" --prop style=Caption --prop size=10pt --prop italic=true --prop spaceAfter=6pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=seq --prop identifier=Table\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\": Participant demographics (N=47).\"\nofficecli add \"$FILE\" /body --type table --prop rows=5 --prop cols=4 --prop width=100%\n# ... fill header + rows per docx v2 §Tables\n```\n\n### Verify SEQ + PAGEREF fields landed\n\n```bash\n# At least one SEQ Figure or SEQ Table in the body document part\nofficecli raw \"$FILE\" /document | grep -c 'w:instrText[^>]*>[^<]*SEQ'   # expect ≥ 1\nofficecli raw \"$FILE\" /document | grep -c 'w:instrText[^>]*>[^<]*PAGEREF' # 0 ok if no cross-refs\n```\n\nLive fields carry **cached values** that render stale until a human presses F9 in Word. Expect \"Figure 1\" to show as `1`, `2`, ... immediately after recalc; before recalc, some viewers render `0` or blank. Judge field presence by `fldChar` existence, not by visible digit (→ see docx v2 §Field / cached-value spot-check).\n\n## Footnotes vs endnotes\n\n**Footnote** — sits at the bottom of the page where its anchor paragraph lives. Used for source citations in Chicago Notes-Bib, content asides in any style.\n\n**Endnote** — sits at the end of the document (or before the bibliography). Used by some venues in place of footnotes, or for long contextual notes that would clutter the page.\n\n```bash\n# Footnote anchored to paragraph N\nofficecli add \"$FILE\" \"/body/p[3]\" --type footnote --prop text=\"Smith et al. reported similar findings in their 2023 review.\"\n# Endnote — anchored to paragraph N (like footnote); lands at /endnote[@endnoteId=N]\nofficecli add \"$FILE\" \"/body/p[3]\" --type endnote --prop text=\"Extended derivation of equation (4) is available at the project repository.\"\n```\n\nBoth appear as empty-string runs in `view annotated` output (`r[N] \"\"`) — the run carries a `<w:footnoteReference>` XML element, not visible text. Confirm insertion with `officecli query \"$FILE\" 'footnote'` or `officecli get \"$FILE\" \"/footnotes/footnote[N]\"`. Footnotes do NOT shift paragraph indices; add them in any order after body content is in place. Full schema: `officecli help docx footnote` / `officecli help docx endnote`.\n\n## Bibliography section\n\nEvery academic paper ends with a reference list. The name of the section depends on the style (**References** for APA / IEEE / Chicago Author-Date; **Bibliography** for Chicago Notes-Bib; **Works Cited** for MLA). Each entry is a separate paragraph with **hanging indent**.\n\n```bash\n# Section heading — same as body Heading1 (excluded from body numbering by convention)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"References\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\n# Each entry: hanging indent 720 twips (0.5\"), with indent=720 as the partner (first line flush, wraps indented)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Smith, J. (2024). Remote work and cohesion. Journal of Applied Psychology, 109(3), 412-430.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\n# DOI hyperlink on its own run appended to the entry paragraph\nofficecli add \"$FILE\" \"/body/p[last()]\" --type hyperlink --prop url=\"https://doi.org/10.1037/apl0001123\" --prop text=\"https://doi.org/10.1037/apl0001123\"\n```\n\nVerified: `--prop indent=720 --prop hangingIndent=720` is the canonical hanging-indent pair per `officecli help docx paragraph`. The old `ind.firstLine=-720` form (negative first-line indent) is NOT canonical and fails schema on emit — → see docx v2 §Schema-invalid-on-emit.\n\n**Round-trip QA.** Count in-text citation markers (APA `(Author, Year)`, IEEE `[N]`, MLA `(Author N)`) vs reference-list entries. See Delivery Gate 4 below. Every cited key must resolve; every listed entry should be cited at least once.\n\n## Multi-column (IEEE journal two-column recipe)\n\nIEEE and many engineering / physics journals render body text in two columns with a single-column abstract above. The mechanism: a section break with `type=continuous` and `columns=2`, then another section break at the end to **revert** to single-column.\n\n**The reversion step is not optional.** Without it, the rest of the document — including references — renders as two columns. This is the single most common multi-column failure.\n\n```bash\nFILE=\"ieee.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\n# 1. Title, authors, affiliation — single-column (the default first section)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Attention-Based Anomaly Detection for Industrial Time Series\" --prop align=center --prop size=18pt --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen, Bob Martinez\" --prop align=center --prop size=11pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Department of CS, Stanford University\" --prop align=center --prop size=10pt --prop spaceAfter=18pt\n\n# 2. Abstract — still single-column, block-style\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Abstract\" --prop align=center --prop size=12pt --prop bold=true --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"We present an attention-based model for detecting anomalies in industrial sensor time series...\" --prop size=10pt --prop lineSpacing=1.15x --prop spaceAfter=12pt\n\n# 3. Section break + two-column from here on\n#    `/section[last()]` resolves to the final section (like p[last()]); an explicit /section[N] also works.\nofficecli add \"$FILE\" /body --type section --prop type=continuous\nSECTION_COUNT=$(officecli query \"$FILE\" section --json | jq '.data.results | length')\n# After the add, SECTION_COUNT should be 2 — [1] is pre-break, [2] is post-break (2-col body area).\nofficecli set \"$FILE\" \"/section[2]\" --prop columns=2 --prop columnSpace=1cm\n\n# 4. Body — IEEE wants Roman numerals + ALL CAPS section titles (P1.2).\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"I. INTRODUCTION\" --prop style=Heading1 --prop size=10pt --prop bold=true\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Industrial anomaly detection has been studied since [1]...\" --prop size=10pt --prop lineSpacing=1.15x --prop firstLineIndent=360\n\n# 5. At the end of 2-column body, ANOTHER section break + revert to single column for references / appendices\n# (If you want references in 2-col too, skip step 5 — but most IEEE papers use 2-col for references as well.)\n# officecli add \"$FILE\" /body --type section --prop type=continuous\n# Use /section[last()] for the final section, or re-count for an explicit /section[N]:\n# officecli set \"$FILE\" \"/section[3]\" --prop columns=1\n\n# 6. Footer, close, validate\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=9pt --prop field=page\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\n**Visual verify.** Run `officecli view \"$FILE\" html` and Read the returned HTML to audit the rendered output. The abstract must render as full-width and the introduction onward as two columns. If the abstract wraps into two narrow columns, the first section break landed before the abstract — move it.\n\n**Section index bookkeeping.** Each `add /body --type section` inserts one empty paragraph into `/body` (the section-break marker). All subsequent `p[N]` indices shift by +1 per section break. Plan section breaks in advance; after adding a break, `officecli get \"$FILE\" /body --depth 1` to re-index before continuing.\n\nFull section schema (`columns`, `columnSpace`, `orientation`, `pageNumFmt`, `titlePage`, `lineNumbers`): `officecli help docx section`.\n\n## Abstract / keywords / affiliation block\n\nFirst-page metadata stack: title (centered 20-22pt bold) → authors (centered 12pt, superscript `^1 ^2` for multi-affiliation) → affiliations (centered 11pt, keyed to superscripts) → submission target / date → **Abstract** heading (14pt bold) → abstract body (block-style, **NO `firstLineIndent`**, 150-300 words) → keywords line (italic 11pt). Same \"cover ≥ 60% filled\" rule as docx v2.\n\n```bash\n# Superscript affiliation markers (multi-institution paper)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen\" --prop align=center --prop size=12pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\"1\" --prop superscript=true\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\", Bob Martinez\"\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\"2\" --prop superscript=true\n# Running header (skip on cover via type=first empty header — see docx v2 §headers)\nofficecli add \"$FILE\" / --type header --prop type=default --prop align=right --prop size=9pt --prop text=\"Short Running Title\"\n```\n\n**Nature-family 2-col abstract** is rare — if required, open a `section type=continuous columns=2` BEFORE the abstract heading; short abstracts (<100 words) leave ragged columns. **Mirrored odd/even headers** are exposed by the high-level API: `officecli set \"$FILE\" /settings --prop evenAndOddHeaders=true`, then add the even header with `--type header --prop type=even`. Full header schema: `officecli help docx header`.\n\n## QA — Delivery Gate (executable)\n\n**Assume there are problems. Your job is to find them.** First render is almost never correct. Run this block before declaring done.\n\n### Gates 1-3 — inherited from docx v2\n\n→ see docx v2 §Delivery Gate. Schema validate, token leak grep, live PAGE field structure. Copy-paste the docx v2 gate block first. Every check must print its success message.\n\n### Gate 4 — citation round-trip\n\nEvery in-text citation key should resolve to a bibliography entry. Count mismatches = REJECT.\n\n```bash\n# IEEE example (bracketed numerics). Adjust regex for APA (Author, Year) or MLA (Author Page).\nCITATIONS=$(officecli view \"$FILE\" text | grep -oE '\\[[0-9]+\\]' | sort -u | wc -l)\nENTRIES=$(officecli query \"$FILE\" 'paragraph[hangingIndent]' --json | jq '.data.results | length')\necho \"In-text citation markers: $CITATIONS | Bibliography entries: $ENTRIES\"\n# REJECT when citations exceed entries (cites without references). Entries > citations is allowed by some venues.\n[ \"$CITATIONS\" -le \"$ENTRIES\" ] && echo \"Gate 4 OK\" || { echo \"REJECT Gate 4: $CITATIONS in-text markers but only $ENTRIES bibliography entries\"; exit 1; }\n```\n\n### Gate 5a — SEQ presence + cached numbers distinct\n\nIf the paper has any numbered figure or table, the body must carry live `SEQ` fields AND their cached values must show distinct ascending numbers (else `view text` and downstream viewers that don't recompute cached fields will show \"Figure 1\" for all).\n\n```bash\n# Count SEQ fields via query (raw-grep collapses multi-matches on one XML line → undercounts).\nSEQ_COUNT=$(officecli query \"$FILE\" 'field[fieldType=seq]' --json | jq '.data.results | length')\nVISIBLE_FIG=$(officecli view \"$FILE\" text | grep -cE '(Figure|Table) [0-9]+')\nif [ \"$VISIBLE_FIG\" -gt 0 ] && [ \"$SEQ_COUNT\" -eq 0 ]; then\n  echo \"REJECT Gate 5a: $VISIBLE_FIG visible Figure/Table labels but 0 SEQ fields.\"\n  exit 1\nfi\n# Cached values must be distinct. Run `set / --prop recalcFields=seq` once after all captions exist;\n# before recalc the fields render the #OCLI_NOTEVAL! sentinel, after recalc Figure 1 / Figure 2 / Figure 3:\nDISTINCT=$(officecli view \"$FILE\" text | grep -oE '(Figure|Table) [0-9]+' | sort -u | wc -l)\n[ \"$SEQ_COUNT\" -le \"$DISTINCT\" ] && echo \"Gate 5a OK (SEQ=$SEQ_COUNT, distinct=$DISTINCT)\" || { echo \"REJECT Gate 5a: $SEQ_COUNT SEQ fields but only $DISTINCT distinct rendered labels — run 'set \\\"$FILE\\\" / --prop recalcFields=seq'\"; exit 1; }\n```\n\n### Gate 5b — Visual audit via HTML preview (MANDATORY, not optional)\n\nGates 1–5a catch schema, token leaks, live-field presence, citation counts. **They do NOT catch physical assembly defects** — scrambled page order, a duplicated Abstract mid-document, three figures all labeled \"Fig. 1\" despite SEQ field presence, equation variables rendering as plain-text LaTeX (`lambda_1`, `x_{t+1}`) instead of math. Do not skip — Gates 1–5a pass ≠ visual OK.\n\nRun `officecli view \"$FILE\" html` and Read the returned HTML path. For every page of the paper, answer:\n\n> (a) Are pages in logical academic sequence? (Title → Abstract → Keywords → Introduction → body → References — no forward jumps, no backward leaks.)\n> (b) Does the Abstract appear exactly once, not duplicated mid-document?\n> (c) Are Figure N / Table N labels distinct and ascending? (Fig. 1, Fig. 2, Fig. 3 — not all \"Fig. 1\". Same for tables.)\n> (d) Do equations render as math? (Italicized variables, Greek letters like λ / α, proper integrals / fractions — NOT plain-text `lambda_1`, `x_{t+1}`, `\\int`.)\n> (e) For IEEE papers: are section titles ALL CAPS with Roman numerals (`I. INTRODUCTION`)? Are tables Roman (`Table I`, `Table II`)?\n> (f) For APA papers: are Level-1 headings centered bold and unnumbered (not `1. Introduction`)?\n> (g) Does every in-text \"see Fig. N\" / \"see Table N\" resolve to a figure/table that actually carries that number?\n> (h) Heading hierarchy visually distinct (size + weight) across H1 / H2 / H3?\n\nReport every instance. If even one defect is present → REJECT; do not deliver until fixed.\n\n**Human preview (optional).** If you want the user to visually preview the paper, run `officecli watch \"$FILE\"` for a live preview the user can open at their own discretion, or have them open the `.docx` directly in Word / WPS / Pages. For final visual verification, open the file in the target viewer.\n\n### Honest limit\n\n`validate` catches schema errors, not academic-style errors. A document passes `validate` with APA citations in an IEEE paper, footnotes in a style that forbids them, or figures with hardcoded numbers that drift when a new figure is inserted. The gates above — especially Gate 4 (round-trip) and Gate 5 (SEQ presence) — are how you catch what validate cannot.\n\n## Known Issues & Pitfalls (academic-specific)\n\n→ Base pitfalls (shell escape, `\\$ \\t \\n` literals, table cell formatting order, page-break boundaries, `shd.fill` / `ind.firstLine` schema-invalid forms, TOC cached values, watermark two-step): see docx v2 §Known Issues & Pitfalls.\n\nAcademic-specific:\n\n- **`\\left(...\\right)` / `\\left[...\\right]` with a sub/superscript INSIDE the delimiters → parse error** (`cast object … Subscript`). An outer script (`\\left(x+y\\right)^2`) is fine. Use plain `(`, `)`, `[`, `]` — OMML auto-sizes in display mode.\n- **`move` on `/body/oMathPara[N]` reorders the equation** (the wrapping paragraph moves). `--before <path>` may leave a stray empty paragraph; prefer `--index` / `--after`.\n- **Section break +1 paragraph offset.** Each `add /body --type section` inserts one empty paragraph into `/body`. All `p[N]` indices after the break shift by +1. Plan breaks; after any `add section`, `officecli get \"$FILE\" /body --depth 1` to re-index.\n- **`/section[last()]` resolves to the final section** (mirrors `p[last()]`), for both get and set. An explicit `/section[N]` also works:\n  ```bash\n  SECTION_COUNT=$(officecli query \"$FILE\" section --json | jq '.data.results | length')\n  # use /section[last()] for the final section, or an explicit /section[2], /section[3], ...\n  ```\n  Each `add /body --type section` increments the count. Re-query after every break.\n- **Multi-column does NOT auto-revert.** After a `columns=2` section, you must add another section break and explicitly set `columns=1` on the new `/section[N]` (N = post-revert count) — otherwise the rest of the document, including references, renders as two columns. Verify with `officecli get \"$FILE\" \"/section[N]\"` for each N.\n- **`--type equation` on a `tc[N]` cell path is rejected with a guard error** (`table cells only accept paragraphs, tables, or SDTs`) — no bad XML is written. Inside a table cell, target `tc[N]/p[1]` with `--prop mode=inline` instead.\n- **SEQ caption numbers are filled by `set / --prop recalcFields=seq`** (run once after all captions exist), not by per-field raw-set patching. A pre-recalc SEQ field shows the `#OCLI_NOTEVAL!` sentinel in `view text`.\n- **Hanging-indent canonical form is `indent=720 hangingIndent=720`.** Not `ind.firstLine=-720`. The dotted form emits `<w:ind>` after `<w:jc>` and fails schema on emit.\n- **Footnote reference runs show as empty strings in `view annotated`.** The `<w:footnoteReference>` XML element has no visible text on the reference side; the note body lives in `/footnotes/footnote[N]`. Confirm with `officecli query \"$FILE\" 'footnote'`, not by eyeballing `view text`.\n- **Caption placement:** Table caption ABOVE the table; Figure caption BELOW the figure. Every major style (APA, Chicago, IEEE, MLA) agrees. Putting a Table caption below the table is an academic-style error, not a rendering issue — `validate` will not catch it.\n- **TOC cached rendering / shell-escape:** → see docx v2 §Table of Contents, §Shell escape.\n\n## Renderer quirks (cross-viewer)\n\n→ see docx v2 §Renderer quirks. PAGE / TOC cached values, OMML baseline shifts, scheme colors — all identical quirks apply to academic papers. Before calling an equation or a citation marker broken, open the file in the user's target viewer (Word, WPS, Pages) — if it renders correctly there, it is a viewer quirk, not a skill defect.\n\n## Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <element> --json`. Help is the authoritative schema; this skill is the decision guide for academic deltas on top of docx v2.\n","skills/morph-ppt-3d/SKILL.md":"---\nname: morph-ppt-3d\ndescription: 3D Morph PPT — extends morph-ppt with GLB model insertion, cinematographic camera, model-content layout, and enriched visual design system.\n---\n\n# Morph PPT — 3D Extension\n\nThis skill **extends** `morph-ppt`. All morph-ppt rules (naming, ghosting, design, verification) apply in full.\nThis file covers **3D-specific additions** and an **enriched design system** combining morph-ppt aesthetics with concrete color palettes, font pairings, and layout quality guardrails.\n\n---\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Use when\n\n- User wants a `.pptx` with a `.glb` 3D model and Morph transitions.\n\n---\n\n## 3D Model Compatibility Gate (before generation)\n\n1. Only `.glb` is supported. If user provides `.fbx` / `.obj` / `.blend` / `.usdz` / `.gltf`, ask them to convert to `.glb` first (e.g. via Blender export).\n2. If user has no model, follow the **Model Discovery Flow** below.\n3. All files (`.glb`, `.pptx`, build script) must be in the same working directory.\n\n---\n\n## Model Discovery Flow (when user has no model)\n\nWhen the user gives a topic but no `.glb` file, **proactively help them find a matching model** instead of just listing websites.\n\n### Step 1: Understand the topic and suggest model direction\n\nBased on the user's topic, suggest what kind of 3D model would work:\n\n| Topic type         | Model suggestion                    | Example                                               |\n| ------------------ | ----------------------------------- | ----------------------------------------------------- |\n| Product/brand      | The actual product or a similar one | \"coffee brand\" → coffee cup, coffee machine, bean     |\n| Animal/character   | The animal or mascot                | \"fox mascot\" → fox 3D model                           |\n| Architecture/space | Building, room, or structure        | \"new office\" → office building, interior              |\n| Vehicle/transport  | The vehicle itself                  | \"EV launch\" → car, motorcycle, bicycle                |\n| Food/cooking       | The dish or ingredient              | \"Japanese food\" → sushi platter, ramen bowl           |\n| Tech/gadget        | The device                          | \"phone launch\" → phone, tablet, laptop                |\n| Nature/science     | The subject                         | \"solar system\" → planet, sun, earth                   |\n| Abstract concept   | A symbolic object                   | \"teamwork\" → puzzle pieces, gears, bridge             |\n\nTell the user: \"Your topic is [X]. I suggest using a 3D model of [description]. Here are some free sources to find one:\"\n\n### Step 2: Search for models (agent-driven)\n\n**Proactively search for models on behalf of the user.** Don't just list websites — actually find candidates.\n\n**Search strategy (try in order):**\n\n1. **Web search** for free GLB models matching the topic:\n\n   ```\n   Search: \"[topic keyword] 3d model glb free download\"\n   Example: \"fox 3d model glb free download\"\n   ```\n\n2. **Sketchfab API** (no auth needed for search):\n\n   ```bash\n   curl -s \"https://api.sketchfab.com/v3/search?type=models&q=[keyword]&downloadable=true&archives_flavours=glb\" \\\n     | python3 -c \"\n   import json, sys\n   data = json.load(sys.stdin)\n   for m in data.get('results', [])[:5]:\n       print(f\\\"Name: {m['name']}\\\")\n       print(f\\\"URL: https://sketchfab.com/3d-models/{m['slug']}-{m['uid']}\\\")\n       print(f\\\"Likes: {m.get('likeCount', 0)}, License: {m.get('license', {}).get('label', 'unknown')}\\\")\n       print()\n   \"\n   ```\n\n3. **Poly Pizza** (direct GLB download, all free):\n\n   ```bash\n   # Search results page — parse for download links\n   curl -s \"https://poly.pizza/api/search/[keyword]\" 2>/dev/null\n   ```\n\n4. **Khronos glTF-Sample-Assets** (guaranteed to work, always available):\n   ```bash\n   # Direct download — no auth, no API, always works\n   curl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/[ModelName]/glTF-Binary/[ModelName].glb\"\n   ```\n   Available models: Duck, Fox, Avocado, BrainStem, CesiumMan, DamagedHelmet, FlightHelmet, Lantern, Suzanne, WaterBottle, etc.\n\n### Step 3: Present candidates to user for confirmation\n\nShow the user 2-3 model options with:\n\n- Model name and source\n- Preview link (Sketchfab URL or description)\n- License info\n- Why this model fits their topic\n\nExample response:\n\n```\nBased on your topic \"fox mascot\", here are some models I found:\n\n1. Fox (Khronos sample)\n   Direct download, guaranteed compatible\n   Why: clean fox model, good for mascot/character decks\n\n2. Low Poly Fox (Poly Pizza)\n   URL: https://poly.pizza/m/xxx\n   License: CC0 (completely free)\n   Why: low-poly style, good fit for clean minimal design\n\n3. Cartoon Fox (Sketchfab)\n   URL: https://sketchfab.com/3d-models/fox-xxx\n   License: CC BY 4.0 (free, commercial use ok)\n   Why: expressive face, high detail\n\nWhich one do you want? I'll download it and start building.\n```\n\n**Wait for user confirmation before downloading.** Do not download without asking.\n\n### Step 4: Download the confirmed model\n\nAfter user confirms, download directly:\n\n```bash\n# For Sketchfab (if user has the download URL)\ncurl -L -o model.glb \"[download_url]\"\n\n# For Khronos samples (always works)\ncurl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Fox/glTF-Binary/Fox.glb\"\n```\n\nAfter download, verify:\n\n- File exists and is not empty\n- File extension is `.glb`\n- File size is under 50MB\n\nIf Sketchfab requires login to download, tell the user:\n\n> \"This model requires a Sketchfab login to download. You can grab the .glb file from the page and share it with me. Or I can use a Khronos sample model for a demo version first?\"\n\n### Step 5: When user says \"anything\" / \"you decide\" / \"just make a demo\"\n\n**Don't just grab a random model.** First guide the user to clarify their PPT topic:\n\n> Sure! I'll handle the model — but let me confirm the topic direction first so the model matches the content:\n>\n> 1. Tech/Product — headphones, phone, robot...\n> 2. Animal/Character — cute pet, cartoon character...\n> 3. Architecture/Space — building, interior, city...\n> 4. Food/Lifestyle — dishes, everyday objects...\n> 5. Other — just tell me your idea\n>\n> Pick a direction, or just give me a topic keyword.\n\nAfter user confirms a direction, THEN search and recommend models.\n\n### Step 6: When user wants to find models themselves\n\nGive specific website links with step-by-step guidance:\n\n> **Recommended 3D model websites:**\n>\n> 1. **Sketchfab** (largest 3D model platform)\n>    - Link: https://sketchfab.com/search?q=[keyword]&type=models&downloadable=true\n>    - Filter steps: search keyword → check \"Downloadable\" → format \"glTF\" → sort by \"Likes\"\n>    - When downloading, select **glTF (.glb)** format\n>    - Note: some models require free registration to download\n> 2. **Poly Pizza** (all free low-poly)\n>    - Link: https://poly.pizza/\n>    - All CC0 licensed — click Download to get .glb directly\n>    - Best for: minimalist or cartoon-style presentations\n> 3. **Sketchfab popular categories**\n>    - Animals: https://sketchfab.com/search?q=animal&type=models&downloadable=true\n>    - Food: https://sketchfab.com/search?q=food&type=models&downloadable=true\n>    - Tech: https://sketchfab.com/search?q=gadget&type=models&downloadable=true\n>    - Architecture: https://sketchfab.com/search?q=architecture&type=models&downloadable=true\n> 4. **Free3D** (general free model site)\n>    - Link: https://free3d.com/3d-models/glb\n>    - Note: check the license type before use\n> 5. **TurboSquid Free** (pro model site free section)\n>    - Link: https://www.turbosquid.com/Search/3D-Models/free/glb\n>\n> After downloading, share the .glb file with me. If the download is a .gltf folder, use Blender to convert it to .glb.\n\n### Step 7: When user gives keywords and asks agent to search\n\n**Remind about token cost before searching:**\n\n> I can search for you, but web searches use extra tokens. Would you prefer:\n>\n> A. I search — I use the Sketchfab API and recommend 2-3 options (uses a few tokens)\n> B. Self-service — I give you search links and filter steps, you pick and share with me (no extra tokens)\n>\n> A or B?\n\nIf user chooses A, proceed with Step 2 (agent-driven search).\nIf user chooses B, proceed with Step 6 (self-service guidance).\n\n### License reminder\n\nAlways remind before confirming download: \"Please check the model license before downloading. CC0 / CC BY = free to use; CC BY-NC = non-commercial only.\"\n\n---\n\n## Visual Design System (4.0 enrichment)\n\nmorph-ppt provides the base design rules. This section adds **concrete palettes, font pairings, and layout quality rules** from PPT Creator to give the AI more variety and stronger guardrails.\n\n### Color Palettes (pick one per deck, or blend)\n\nChoose a palette that matches the **topic mood** — don't default to generic blue.\n\n| Palette                | Primary               | Secondary             | Accent           | Body Text | Muted/Caption |\n| ---------------------- | --------------------- | --------------------- | ---------------- | --------- | ------------- |\n| **Coral Energy**       | `F96167` (coral)      | `F9E795` (gold)       | `2F3C7E` (navy)  | `333333`  | `8B7E6A`      |\n| **Midnight Executive** | `1E2761` (navy)       | `CADCFC` (ice blue)   | `FFFFFF`         | `333333`  | `8899BB`      |\n| **Forest & Moss**      | `2C5F2D` (forest)     | `97BC62` (moss)       | `F5F5F5` (cream) | `2D2D2D`  | `6B8E6B`      |\n| **Charcoal Minimal**   | `36454F` (charcoal)   | `F2F2F2` (off-white)  | `212121`         | `333333`  | `7A8A94`      |\n| **Warm Terracotta**    | `B85042` (terracotta) | `E7E8D1` (sand)       | `A7BEAE` (sage)  | `3D2B2B`  | `8C7B75`      |\n| **Berry & Cream**      | `6D2E46` (berry)      | `A26769` (dusty rose) | `ECE2D0` (cream) | `3D2233`  | `8C6B7A`      |\n| **Ocean Gradient**     | `065A82` (deep blue)  | `1C7293` (teal)       | `21295C`         | `2B3A4E`  | `6B8FAA`      |\n| **Teal Trust**         | `028090` (teal)       | `00A896` (seafoam)    | `02C39A` (mint)  | `2D3B3B`  | `5E8C8C`      |\n| **Sage Calm**          | `84B59F` (sage)       | `69A297` (eucalyptus) | `50808E`         | `2D3D35`  | `7A9488`      |\n| **Cherry Bold**        | `990011` (cherry)     | `FCF6F5` (off-white)  | `2F3C7E` (navy)  | `333333`  | `8B6B6B`      |\n\n**Rules:**\n\n- One color dominates (60-70% visual weight), 1-2 supporting tones, one accent\n- On light backgrounds: use Body Text color for copy, Muted for captions\n- On dark backgrounds: use Secondary or `FFFFFF` for copy, Muted for captions\n- For additional inspiration, browse `../morph-ppt/reference/styles/INDEX.md` — 50+ visual styles organized by mood (dark, light, warm, vivid, bw). Read `style.md` for design philosophy, `build.sh` for implementation reference. **Learn the approach, do not copy coordinates verbatim**\n\n### Font Pairings (pick one per deck)\n\n| Header Font  | Body Font     | Best For                         |\n| ------------ | ------------- | -------------------------------- |\n| Georgia      | Calibri       | Formal business, finance         |\n| Arial Black  | Arial         | Bold marketing, product launches |\n| Calibri      | Calibri Light | Clean corporate, minimal         |\n| Cambria      | Calibri       | Traditional professional         |\n| Trebuchet MS | Calibri       | Friendly tech, startups          |\n| Impact       | Arial         | Bold headlines, keynotes         |\n| Palatino     | Garamond      | Elegant editorial, luxury        |\n| Consolas     | Calibri       | Developer tools, technical       |\n\n### Hard Rules (mandatory, no exceptions)\n\n**H4 — Body text minimum 16pt:**\nAll body text, card content, and bullet points must be >= 16pt. \"Content doesn't fit\" is not an excuse — reduce text, split slides, or reduce card count instead. Exceptions: chart axis labels (<=12pt), short sublabels (<=14pt, max 5 words), footnotes.\n\n**H6 — Dark background contrast:**\nWhen slide background brightness < 30% (e.g. `1E2761`, `36454F`, `000000`), ALL body text, card content, chart labels, and icon fills MUST use white (`FFFFFF`) or near-white (brightness > 80%). Never use mid-gray or muted colors as body text on dark backgrounds.\n\n**H7 — Speaker notes required:**\nEvery content slide (not title/closing) MUST have speaker notes. Use:\n\n```bash\nofficecli add deck.pptx '/slide[N]' --type notes --prop text=\"...\"\n```\n\n### Visual Element Checkpoint\n\n**Every 3 content slides, at least 1 must contain a non-text visual element:**\n\n| Visual type            | Implementation                               |\n| ---------------------- | -------------------------------------------- |\n| Icon in colored circle | ellipse shape + centered text/number overlay |\n| Colored block          | `preset=roundRect` with fill                 |\n| Large stat number      | `size=64, bold=true` with small label below  |\n| Chart                  | `--type chart` (column/pie/line)             |\n| Gradient background    | `background=COLOR1-COLOR2-180`               |\n| Shape composition      | circles + connectors for diagrams            |\n\nText-only slides are only allowed for: quotes, code examples, pure tables.\n\n---\n\n## 3D Model Insertion Rules\n\n### Add model fresh on every slide — NEVER clone\n\n`morph_clone_slide` copies the model as frozen XML. The cloned model cannot Morph.\nEach slide must call `add --type 3dmodel` independently with the **same `name`** prop.\n\n**⚠️ CRITICAL: If you clone a slide that already has a 3D model, the old model XML is copied too. This creates TWO model3d elements with the same name on the new slide. PowerPoint cannot handle this conflict and will delete the model content during repair.**\n\nIf you must clone a slide for scene actors, **immediately remove the cloned model before adding a new one:**\n\n```bash\n# After cloning slide 1 to slide 2:\nofficecli remove deck.pptx '/slide[2]/model3d[1]'  # remove the frozen clone\nofficecli add deck.pptx '/slide[2]' --type 3dmodel ...  # add fresh model\n```\n\n**Recommended approach: Do NOT clone slides with 3D models at all.** Create all slides empty first, then add models fresh on each.\n\n```bash\n# Slide 1\nofficecli add deck.pptx '/slide[1]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=16cm --prop y=1cm --prop width=16cm --prop height=16cm \\\n  --prop roty=0\n\n# Slide 2\nofficecli add deck.pptx '/slide[2]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=0.5cm --prop y=1cm --prop width=18cm --prop height=17cm \\\n  --prop roty=50\n```\n\n### Controllable properties\n\n| Property          | What it does              | Notes                                         |\n| ----------------- | ------------------------- | --------------------------------------------- |\n| `x`, `y`          | Position on slide         | Standard slide coordinates                    |\n| `width`, `height` | Frame size                | Model renders inside this frame               |\n| `name`            | Shape name                | Must be identical across slides for Morph     |\n| `roty`            | Y-axis rotation (degrees) | Primary storytelling axis                     |\n| `rotx`            | X-axis tilt (degrees)     | Range -25 to +40. See Camera Language section |\n| `rotz`            | Z-axis roll (degrees)     | Rarely needed                                 |\n\n### Do NOT manually set\n\n- `meterPerModelUnit` — auto-computed from GLB bounding box\n- `preTrans` — auto-computed for model centering\n- `camera` depth/position — auto-computed to fit the model\n- Never use `raw-set` on any 3D transform parameter\n\n---\n\n## Model-Content Layout\n\n### Core Principle: Model IS the Subject\n\nThe model must feel like the **protagonist** of the presentation, not a sidebar decoration.\nText supports the model; the model does not decorate the text.\n\n### Size Contrast Rule (MANDATORY)\n\nAdjacent slides must have a model area ratio >= 1.5x or <= 0.67x.\nCompute area as `width × height`. If slide N model is 16×15=240 cm², slide N+1 must be >= 360 or <= 160.\n\n**Never use similar sizes on consecutive slides.** This is the single most important rule for visual energy.\n\n| Size tier      | Width   | Height  | Area (approx) | When to use                                |\n| -------------- | ------- | ------- | ------------- | ------------------------------------------ |\n| **XL (bleed)** | 28-36cm | 22-28cm | 600-1000      | Close-up, model extends beyond slide edges |\n| **L (hero)**   | 18-24cm | 15-19cm | 270-456       | Title, closing, dramatic moments           |\n| **M (split)**  | 13-17cm | 12-16cm | 156-272       | Standard content pages with text           |\n| **S (accent)** | 5-10cm  | 5-10cm  | 25-100        | Data-heavy pages, model as icon            |\n\n### Layout Patterns (6 types)\n\n**A — Model right, content left** (content pages)\nContent at x=1-14cm. Model at x=15-20cm, width 14-18cm.\n\n**B — Model left, content right** (alternate with A)\nModel at x=0-2cm, width 14-18cm. Content at x=18-32cm.\n\n**C — Model centered, text overlay** (title/closing)\nModel centered large (18-24cm). Text at slide top or bottom.\n\n**D — Model small corner, content dominant** (data pages)\nModel 5-10cm in any corner. Content fills the rest.\n\n**E — Model as backdrop** (impact/quote pages)\nModel XL (28-36cm), centered, partially cropped by slide edges.\nText overlaid directly on top of model area with high-contrast color.\nThe model becomes the \"canvas\" — text lives inside the model's space.\n\n```bash\n# Pattern E: model fills slide as backdrop\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=-2cm --prop y=-2cm --prop width=38cm --prop height=24cm \\\n  --prop roty=45 --prop rotx=10\n\n# Text overlaid on model\nofficecli add deck.pptx '/slide[N]' --type shape \\\n  --prop 'name=#sN-quote' --prop text=\"Key insight here\" \\\n  --prop x=3cm --prop y=7cm --prop width=28cm --prop height=5cm \\\n  --prop size=44 --prop bold=true --prop color=FFFFFF --prop fill=none\n```\n\n**F — Model bleed edge** (transition/teaser pages)\nModel partially off-screen (negative x or y, or x+width > 33.87cm).\nOnly part of the model visible — implies more beyond the frame.\n\n```bash\n# Pattern F: model bleeds off right edge\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=20cm --prop y=-1cm --prop width=24cm --prop height=22cm \\\n  --prop roty=70\n```\n\n### Layout Progression\n\nNever repeat the same pattern on consecutive slides. Example:\n\n```\nSlide 1: C (centered hero, L)\nSlide 2: E (backdrop close-up, XL)   ← 1.5x+ area jump\nSlide 3: A (model right, M)          ← pull back\nSlide 4: F (bleed edge, L)           ← push in\nSlide 5: D (small corner, S)         ← dramatic pull back\nSlide 6: B (model left, M)           ← grow\nSlide 7: C (centered closing, L)     ← push in\n```\n\n### Text Layout Safety (MANDATORY)\n\n**Text boxes must never overlap each other or the model frame.**\n\nRules:\n\n1. **Title and body must not collide.** If a title wraps to 2 lines, the body `y` must account for the title's actual height, not the planned height. Safe formula: `body_y = title_y + title_height + 0.5cm`\n2. **Fixed-height text boxes are dangerous.** If text content is longer than expected, it will overflow invisibly. Use generous heights: title `3-4cm`, body `6-8cm`, bullets `8-10cm`.\n3. **Model frame and text boxes: gap >= 1cm.** Calculate: if model is at `x=15cm`, text `x + width` must be <= `14cm`.\n4. **On Pattern C (centered model + text overlay):** text goes at slide top (`y=0.5-2cm`) or bottom (`y=14-17cm`), NOT in the vertical middle where the model lives (`y=3-13cm`).\n5. **After building each slide, verify coordinates:**\n   ```bash\n   officecli get deck.pptx '/slide[N]' --depth 1\n   # Check: no two shapes share overlapping x/y/width/height ranges\n   ```\n\n### Model Bleed Guidelines\n\n**Not every model looks good when cropped.** Bleed (Pattern E/F) works best for:\n\n- ✅ Symmetric objects (spheres, helmets, bottles) — any crop looks intentional\n- ✅ Large flat surfaces (cars, buildings) — partial view implies scale\n- ✅ When cropping non-critical parts (background, base, stand)\n\nBleed does NOT work for:\n\n- ❌ Character/animal models — cropping ears, tails, or limbs looks broken\n- ❌ Small detailed models — cropping loses the detail you want to show\n- ❌ When the cropped part is the most recognizable feature\n\n**For character/animal models (like fox, duck, avocado):** keep the full model visible on all slides. Use size changes (L→M→S) for rhythm instead of bleed cropping. Use `rotx` for angle variety instead.\n\n---\n\n## Camera Language\n\nThree tools work together: **roty** (orbit), **rotx** (tilt), **width/height** (zoom).\n\n### Shot Types (use >= 3 different per deck)\n\n| Shot                     | Size                  | rotx       | When                        |\n| ------------------------ | --------------------- | ---------- | --------------------------- |\n| **Establishing**         | L (18-24cm)           | 0-5        | Title, intro, closing       |\n| **Three-quarter beauty** | L (16-20cm)           | 5-10       | Hero, first impression      |\n| **Close-up**             | XL (28-36cm), cropped | 0-10       | Feature highlight, detail   |\n| **Bird's eye**           | M (13-17cm)           | 25-40      | Structure, overview         |\n| **Low angle**            | L (16-20cm)           | -15 to -25 | Power, drama                |\n| **Side profile**         | M (13-16cm)           | 0          | Form factor, silhouette     |\n| **Over-the-shoulder**    | S (5-10cm)            | 10-15      | Data-heavy, model as accent |\n\n### Content-Driven Camera\n\nMatch the shot to what the slide talks about:\n\n- \"Front design\" → Close-up, `roty=0`, XL cropped\n- \"Side profile\" → Side, `roty=90`, M\n- \"Internal structure\" → Bird's eye, `roty=30, rotx=35`, M\n- \"Power/authority\" → Low angle, `roty=20, rotx=-20`, L\n- \"Data & specs\" → Over-the-shoulder, `roty=60`, S in corner\n\n### Rotation Rules\n\n1. Adjacent roty delta: 30-90° (< 30 = jitter, > 90 = disorienting)\n2. Overall roty direction must be consistent (no back-and-forth)\n3. rotx range: -25 to +40. Adjacent rotx delta <= 20\n4. Total arc across deck: 180-360° (show the model from all sides)\n\n### Example Shot Plan\n\n| Slide | Shot                 | roty | rotx | Size     | Pattern |\n| ----- | -------------------- | ---- | ---- | -------- | ------- |\n| 1     | Three-quarter beauty | 30   | 8    | L 20×17  | C       |\n| 2     | Close-up             | 0    | 5    | XL 30×24 | E       |\n| 3     | Side profile         | 80   | 0    | M 15×14  | A       |\n| 4     | Bird's eye           | 120  | 35   | M 14×13  | B       |\n| 5     | Low angle            | 170  | -20  | L 20×18  | F       |\n| 6     | Over-the-shoulder    | 220  | 10   | S 8×7    | D       |\n| 7     | Establishing         | 320  | 5    | L 20×17  | C       |\n\n---\n\n## Workflow Integration with morph-ppt\n\n### Phase 2 additions (Planning)\n\nIn `brief.md`, add a **Model Choreography Table**:\n\n| Slide | Pattern | Size Tier | Model x,y,w,h | roty | rotx |\n| ----- | ------- | --------- | ------------- | ---- | ---- |\n| 1     | C       | L         | 7,0.5,20,17   | 30   | 8    |\n| 2     | E       | XL        | -2,-2,38,24   | 0    | 5    |\n| ...   | ...     | ...       | ...           | ...  | ...  |\n\nVerify the area ratio rule (>= 1.5x between adjacent rows) before proceeding to build.\n\n### Phase 3 additions (Build)\n\nSince models cannot be cloned, the build script differs from standard morph-ppt:\n\n1. Create all slides first (with background + morph transition)\n2. Add scene actors (`!!scene-*`) on slide 1, then clone slides for morph continuity\n3. Add 3D model fresh on EACH slide (same name, different roty/position)\n4. Add content shapes per slide, ghost previous content\n\n```python\nmodel_positions = [\n    {\"slide\": 1, \"x\": \"7cm\",  \"y\": \"0.5cm\", \"w\": \"20cm\", \"h\": \"17cm\", \"roty\": 30},\n    {\"slide\": 2, \"x\": \"-2cm\", \"y\": \"-2cm\",  \"w\": \"38cm\", \"h\": \"24cm\", \"roty\": 0},\n    {\"slide\": 3, \"x\": \"16cm\", \"y\": \"1cm\",   \"w\": \"15cm\", \"h\": \"14cm\", \"roty\": 80},\n    # ...\n]\nfor pos in model_positions:\n    run(\"officecli\", \"add\", OUTPUT, f\"/slide[{pos['slide']}]\", \"--type\", \"3dmodel\",\n        \"--prop\", f\"path={MODEL}\", \"--prop\", \"name=!!model-hero\",\n        \"--prop\", f\"x={pos['x']}\", \"--prop\", f\"y={pos['y']}\",\n        \"--prop\", f\"width={pos['w']}\", \"--prop\", f\"height={pos['h']}\",\n        \"--prop\", f\"roty={pos['roty']}\")\n```\n\n### Phase 4 additions (Verification)\n\nAfter standard morph verification, additionally check:\n\n- Each slide has exactly one `model3d` element\n- All models share the same `name` prop\n- Adjacent slides have model area ratio >= 1.5x or <= 0.67x\n- No two consecutive slides use the same layout pattern\n\n---\n\n## File Placement Rule\n\nAll files must be in the same working directory.\n\n**Deliverables (exactly 4 files, no more):**\n\n- `.glb` model file (the 3D model used in the deck)\n- Output `.pptx`\n- Build script (re-runnable)\n- `brief.md`\n\n**Do NOT create additional files** such as outline.md, quality-report.md, test-report.md, etc. All planning goes in `brief.md`, all verification output goes to stdout. Extra files confuse users.\n\nDo not scatter model files across unrelated paths.\n","skills/morph-ppt/SKILL.md":"---\nname: morph-ppt\ndescription: \"Use this skill when the user wants a .pptx with smooth cross-slide animation — PowerPoint Morph transitions, Keynote-style continuous motion, shapes that grow / move / rotate as the slide advances. Trigger on: 'morph', 'morph transition', 'smooth transition', 'continuous animation across slides', 'Keynote-style transition', 'animated slide sequence', 'shape continuity across slides'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate 1–5a). DO NOT invoke for a generic deck, pitch deck, or board review without cross-slide motion — route those to officecli-pptx base or officecli-pitch-deck.\"\n---\n\n# OfficeCLI Morph-PPT Skill\n\n**This skill is a scene layer on top of `officecli-pptx`.** Every pptx hard rule — visual delivery floor (title ≥ 36pt / body ≥ 18pt / title ≥ 2× body), 12-column grid on 33.87×19.05cm, canonical palettes, chart-choice decision table, connector canon, shell escape, resident + batch, Delivery Gate 1–5a — is inherited, not re-taught. This file adds only what **Morph** needs on top: cross-slide shape-name binding, Scene Actors vs content prefixing, ghost discipline, `transition=morph` CLI quirks, 52-style visual library lookup, and a morph-specific fresh-eyes Gate 5b extension.\n\nWhen the pptx base rules cover it, the text here says `→ see pptx v2 §X`. Read `skills/officecli-pptx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches the Morph workflow — when shape names must match, when to ghost, when the CLI auto-prefixes — not every command flag.** When a prop name, enum, or preset is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help pptx slide           # authoritative for: transition, advanceTime, advanceClick, background\nofficecli help pptx transition      # transition / transitionDuration / transitionSpeed (Parent: slide)\nofficecli help pptx shape           # name, preset, x/y/width/height, fill, rotation, opacity, animation\nofficecli help pptx animation       # preset + trigger + duration values\nofficecli help pptx <element> --json  # machine-readable schema\n```\n\nHelp reflects the installed CLI version. When skill and help disagree, **help wins.** Every `--prop X=` in this file is grep-verified against `officecli help pptx <element>`. Specific confirmations: `transition=morph` is a listed value on `slide`; `advanceTime` / `advanceClick` are valid. `transition` is a real element (`officecli help pptx transition`, Parent: slide, set/get) — it exposes `transition`, `transitionDuration`, and `transitionSpeed`. Set the transition with the high-level path `set <slide> --prop transition=morph`; tune speed/duration with the combined shorthand `transition=morph-slow` (or `-fast`, or `transition=morph-<DUR_MS>`). Speed/duration are set only via that shorthand on the `transition` prop, not as independent sub-props. Both round-trip on readback: `transition=morph-slow`/`-fast` reads back as `transitionSpeed=slow`/`fast`, and `transition=morph-<DUR_MS>` (e.g. `morph-1500`) reads back as `transitionDuration=1500`.\n\n## Mental Model & Inheritance\n\n**Inherits pptx v2.** You should have read `skills/officecli-pptx/SKILL.md` first. This skill assumes you know how to: add slides + shapes + charts + connectors; address by `@name=` / `@id=`; quote paths; use `batch` heredocs; use `tailEnd=triangle` on flow connectors; run the Delivery Gate 1–5a; attribute `[AGENT-ERROR]` vs `[RENDERER-BUG]` vs `[SKILL gap]`. If any of those are unfamiliar, read pptx v2 first.\n\n**Inherited from pptx v2 (do NOT re-teach):**\n\n- Visual delivery floor — title ≥ 36pt / body ≥ 18pt / title ≥ 2× body, cover-richness, contrast floor, no `\\$\\t\\n` literals, ≤ 1 animation per slide / ≤ 600ms.\n- Grid math — 33.87 × 19.05cm, edge margin ≥ 1.27cm, inter-block gap ≥ 0.76cm, ≥ 20% negative space. For N-card grids: `col = (33.87 − 2·margin − (N−1)·gap) / N`.\n- Four canonical palettes (Executive navy / Forest & moss / Warm terracotta / Charcoal minimal) — morph decks may pick a different mood from `reference/styles/`, but contrast rules still apply.\n- Chart-choice table — column vs bar vs line vs pie vs scatter vs large-text KPI; `> 3 series + > 8 categories` = split.\n- Connector canon — `shape=straight|elbow|curve`, `@id=` for from/to (C-P-6), `tailEnd=triangle` on every flow.\n- Shell escape 3-layer — `$` single-quoted, heredocs for batch, `<a:br/>` for real newlines.\n- Resident mode + batch ≤ 12 ops, `<<'EOF'` single-quoted delimiter.\n- Delivery Gate 1-5a (schema, token grep, hyperlink rPr, slide-order, dark-on-dark) — every gate prints OK before declaring done.\n- Known Issues C-P-1..7 (hyperlink rPr, chart spPr warning, animation duration readback, animation remove, connector enum, connector `@name=`, chart color renderer normalization).\n- Attribution triage — `[AGENT-ERROR]` vs `[RENDERER-BUG]` vs `[SKILL gap]`.\n\n**Morph identity — what this skill owns (delta on top of pptx v2):**\n\n- **Cross-slide shape-name binding.** PowerPoint's Morph engine pairs shapes by **identical `name=`** across adjacent slides and interpolates their position / size / rotation / fill / opacity. No matching name ⇒ no animation, silent fade. This is a workflow discipline, not a CLI feature.\n- **Namespace prefixes:** `!!scene-*` (persistent decoration, never ghosted) / `!!actor-*` (content that evolves then exits) / `#sN-*` (per-slide content, ghosted on slide N+1). Plan the names BEFORE you `add`.\n- **Ghost position `x=36cm`** (off the right edge of the 33.87cm canvas). Never delete a `!!`-prefixed shape — move it off-canvas so the morph exit animation still plays.\n- **`transition=morph` auto-prefix quirk.** The CLI auto-prepends `!!` to every shape on a morph slide (`#s1-title` is stored as `!!#s1-title`). `@name=` path selectors **still resolve** — `get .../shape[@name=#s1-title]` returns the shape (matching is suffix/prefix-tolerant). The name you read back is the prefixed form. See §Known Issues.\n- **Adjacent-slide spatial variety.** Displacement ≥ 5cm or rotation ≥ 15° between pairs — otherwise morph interpolates nothing visible.\n- **Renderer reality.** Morph renders in PowerPoint 365 / Keynote / WPS. LibreOffice and many web viewers render as plain fade (runtime feature). Not a skill defect — `[RENDERER-BUG]`.\n\n### Reverse handoff — when to go BACK to pptx base (or sibling skills)\n\nStay in **pptx v2 base** for any deck without cross-slide motion (board reviews, sales decks, all-hands, training). Stay in **officecli-pitch-deck** for fundraising narrative arcs without morph. Use this skill only when the user explicitly asks for \"morph\" / \"smooth transitions\" / \"continuous animation\" AND ≥ 2 consecutive slides share a visual element that transforms. \"Animated deck\" meaning one-off entrance animations → pptx v2 §Animations, not morph.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see pptx v2 §Shell & Execution Discipline. Same rules verbatim.\n\n**Morph-specific additions:**\n\n- **`!!` in shell values — single-quote.** Bash / zsh history expansion eats unquoted `!!foo`. Always use `--prop 'name=!!scene-ring'` (single quotes). In Python `subprocess.run([...])` lists, no quoting needed — pass `\"name=!!scene-ring\"` as a plain string.\n- **`$` in prop text — single-quote (price tokens).** `--prop text='$9/mo'` and `--prop text='$199/yr'` — NEVER `--prop text=\"$9/mo\"` (zsh/bash eat `$9` as empty var → text rendered as `.` / stray period). Same for `${VAR}`, `$USER`, `\\n`, `\\r`, `\\t` inside a double-quoted prop. Gate 2 morph addendum below greps for the leak signature.\n- **`#` in shell values — safe, but quote anyway.** `#` is a comment leader only at the start of a shell word. `--prop name=#s1-title` works, but `--prop 'name=#s1-title'` is the habit that stops you guessing.\n- **Batch heredoc is the cleanest path for multi-shape slides.** `<<'EOF' | officecli batch $FILE` disables all shell expansion — safe for `$`, `!!`, `#`, `'` inside the JSON body.\n- **`--json` responses wrap the payload in `.data.results[]`.** Both `query` and `get` return a `.data.results[]` array. A single node's `format` sits at `.data.results[0].format.X`; that node's children sit at `.data.results[0].children[]` (each child's format at `.data.results[0].children[].format.X`). Always go through `.data.results[0]` — bare `.data.children[]` or `.data.format` returns null silently.\n- **Variable:** `FILE=\"deck.pptx\"` at the top of every build script; every example below uses `$FILE`.\n- **Gate shell pattern — COUNT, then if/else.** Never write `grep … && echo LEAK || echo OK` — when grep exits 1 (0 matches), the `||` branch fires with empty stdout and prints \"OK\" confusingly (or prints \"LEAK\" from prior pipes). Canonical form: `COUNT=$(cmd | wc -l); if [ \"$COUNT\" -gt 0 ]; then echo \"LEAK: …\"; else echo \"OK\"; fi`.\n\n## Two primitives this skill owns\n\n- **Scene Actors** = persistent `!!`-named shapes (decoration or content) **paired by identical name** across adjacent slides so Morph can interpolate them. Every `!!scene-*` / `!!actor-*` shape is a scene actor.\n- **Choreography** = the plan for how actors evolve — who moves where, who enters, who exits, on which slide pair. Written BEFORE code in the §Morph Pair Planning table.\n\nUse this skill when the user asks for morph motion AND ≥ 2 consecutive slides share a visual element that transforms. Target-viewer caveat: morph needs PowerPoint 365 / Keynote / WPS — if the user is LibreOffice-only, warn first (see §Renderer honesty).\n\n**Speaker notes rule.** Every content slide (non-cover, non-closing) MUST carry speaker notes via `officecli add \"$FILE\" /slide[N] --type notes --prop text='…'`. Missing notes = not shippable — inherits pptx v2 §Hard rules (H7). Morph decks tend to be visually minimal, so notes carry the narration.\n\n## What is Morph? (core mechanics)\n\nPowerPoint's Morph transition creates smooth motion by interpolating shape properties between adjacent slides, matched by **identical shape names**.\n\n```\nSlide 1: shape name=\"!!scene-ring\" x=5cm  width=8cm   fill=E94560 opacity=0.3\nSlide 2: shape name=\"!!scene-ring\" x=20cm width=12cm fill=E94560 opacity=0.6\n         ↓  transition=morph on slide 2\nResult:  Ring smoothly moves, grows, and fades darker over ~1 second\n```\n\nMorph only runs if slide N+1 carries `transition=morph`. Apply it via `officecli add / --type slide --prop transition=morph` on creation, or `officecli set \"/slide[N]\" --prop transition=morph` after the fact. Slides 2+ that omit this prop fall back to whatever the master defines (usually no transition) — motion dies silently.\n\n**Three-prefix naming system (non-negotiable):**\n\n| Prefix | Role | Lifecycle | Example |\n|---|---|---|---|\n| `!!scene-*` | Background / decoration — persists across the entire deck | Set once, adjust position/size to create motion; **rarely ghosted** | `!!scene-ring`, `!!scene-bg-band`, `!!scene-grid` |\n| `!!actor-*` | Content / foreground — evolves across a section | Introduced on slide N, modified on slide N+1, N+2…, **ghosted to `x=36cm`** on its exit slide | `!!actor-feature-box`, `!!actor-metric`, `!!actor-headline` |\n| `#sN-*` | Per-slide content (titles, bullets, captions) | Added fresh on slide N, **ghosted to `x=36cm`** on slide N+1 | `#s1-title`, `#s2-kpi`, `#s3-caption` |\n\n**Hard rule:** `!!scene-*` and `!!actor-*` names must NEVER collide (e.g., `!!scene-card` + `!!actor-card` in the same deck — morph engine confuses them). Disambiguate: `!!scene-card-bg` vs `!!actor-card-content`.\n\n**Charts can be morph-paired.** `officecli add … --type chart` accepts `--prop name=!!…` (the name reads back), so a chart with an identical `!!`-name on adjacent slides participates in shape-name morph pairing — the chart frame interpolates position / size. Note morph cannot interpolate the *plotted data* inside the chart frame. For bar-grow / line-grow narratives where the bars themselves must animate: (a) accept plain fade-in of the chart as-is, OR (b) build N `!!actor-bar-K` rectangles manually sized to the values and morph those — each rect carries the same `!!actor-bar-K` name across adjacent slides while width / height / fill evolves.\n\n**Ghost accumulation is silent.** Once a `!!`-prefixed shape appears on any slide, it stays visible on every subsequent morph slide unless explicitly moved to `x=36cm`. `final-check` helper does NOT detect `!!` shapes lingering in the visible area — **only Gate 5b screenshot audit does.** Plan every actor's exit slide in the pair table BEFORE coding.\n\n**Spatial variety rule.** Adjacent slides must have **noticeably different** compositions — displacement ≥ 5cm OR rotation ≥ 15° OR size delta ≥ 30% on at least 3 morph-paired shapes. Without this, morph interpolates nothing visible and the transition collapses to a fade (silent-fail).\n\n**Simultaneous-timing constraint.** All `!!` shapes in one morph pair animate simultaneously. To stagger shape A before shape B, insert an intermediate keyframe slide — there is no per-shape delay knob.\n\n**Paired vs enter vs exit — three behaviors, one rule.** Same mechanism (shape-name match) produces three outcomes:\n\n| Behavior | Source slide A | Target slide B | Who carries `!!`? |\n|---|---|---|---|\n| **Paired morph** (interpolate) | has `!!foo` | has `!!foo` | both slides, identical name |\n| **Enter** (fade / morph-in) | — (no counterpart) | has `!!foo` | target only — new shape |\n| **Exit via ghost** (slide off) | has `!!foo` at visible `x` | has `!!foo` at `x=36cm` | both — same name, B is off-canvas |\n\n**Outgoing content (not incoming) is what gets `!!`-prefixed + ghosted.** `!!actor-*` shapes silently \"disappear\" when you forget them — their name going missing on slide B reads as an unpaired exit (plain fade). Always explicit-ghost to `x=36cm` so the exit animation slides off the right edge visibly. One runnable example:\n\n```bash\n# Slide 2: actor is visible at x=5cm — Slide 3: same name, ghosted off-canvas → visible slide-off motion\nofficecli add \"$FILE\" \"/slide[3]\" --type shape --prop 'name=!!actor-metric' \\\n  --prop text=\"42%\" --prop x=36cm --prop y=8cm --prop width=6cm --prop height=3cm\n```\n\n**Content (`#sN-*`) is added fresh per slide.** Because text changes every slide, Morph has no meaningful pairing to do on titles / body — it cross-fades them. This is why `#sN-*` get different names per slide (they are intentionally unpaired) and must be ghosted on slide N+1. Scene actors (`!!`) carry the continuity; content (`#`) carries the message.\n\n## Morph Pair Planning (pre-code, REQUIRED)\n\nBefore planning morph pairs, if the deck's audience / purpose / narrative is underspecified, run the planning prompt in `reference/decision-rules.md` to emit a `brief.md` first — a morph arc without a narrative spine collapses into \"slide with motion\", not \"story with motion\".\n\nPlan every transition in a table inside `brief.md` **before** writing any `officecli add`. Renaming shapes mid-build is the #1 cause of ghost accumulation bugs.\n\n| Pair | Slide A (start) | Slide B (end) | Actors in play | Ghost on Slide B |\n|---|---|---|---|---|\n| 1→2 | `!!scene-ring` centered 5cm, `#s1-title` visible | Ring shifts to x=20cm, grows 8→12cm; `#s2-subtitle` revealed | `!!scene-ring` evolves | `#s1-title` → x=36cm |\n| 2→3 | `!!actor-feature-box` large (14cm wide) | Feature box small (6cm), `!!actor-metric` enters | `!!scene-ring`, `!!actor-feature-box`, `!!actor-metric` | `#s2-subtitle` → x=36cm |\n| 3→4 | Content section A | Section B divider | — | `!!actor-feature-box` + `!!actor-metric` → x=36cm (section-exit); `#s3-*` → x=36cm |\n\n**Planning rules:**\n\n1. Decide ALL `!!` names up front — each morph-paired shape must use the **exact same name** on both slides.\n2. Classify every `!!` shape as `!!scene-*` or `!!actor-*`. Scene shapes persist; actors must have a planned exit slide.\n3. **Section-transition boundary:** when moving into a new topic section, ghost ALL previous-section `!!actor-*` on the first slide of the new section. Only `!!scene-*` (whole-deck decoration) remains.\n4. Do NOT start building until the table is complete. If the plan changes mid-build, redraw the table and re-verify affected slides.\n\n## Morph Recipes (4 patterns)\n\nFour patterns cover ~95% of morph decks. `$FILE=\"deck.pptx\"` throughout. Each block is self-contained and ≤ 20 lines.\n\n### (a) Single-element morph — size / position\n\n**Visual outcome.** A hero title centered on slide 1 (size 48pt at y=8cm), then slide 2 shrinks it to 32pt and shifts it to the top-left corner (x=1.5cm, y=1cm) — letting fresh slide-2 content take center stage. One shape, clean motion, no actors.\n\n```bash\nFILE=\"deck.pptx\"\nofficecli create \"$FILE\"; officecli open \"$FILE\"\n\n# Slide 1 — hero\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!actor-headline' \\\n  --prop text=\"The one idea\" --prop x=4cm --prop y=8cm --prop width=26cm --prop height=3cm \\\n  --prop font=Georgia --prop size=48 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\n\n# Slide 2 — headline shrinks + moves; new body takes stage\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761 --prop transition=morph\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!actor-headline' \\\n  --prop text=\"The one idea\" --prop x=1.5cm --prop y=1cm --prop width=12cm --prop height=1.5cm \\\n  --prop font=Georgia --prop size=24 --prop bold=true --prop color=FFFFFF --prop align=left --prop fill=none\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=#s2-body' \\\n  --prop text=\"Here is the supporting evidence.\" --prop x=1.5cm --prop y=5cm --prop width=30cm --prop height=2cm \\\n  --prop font=Calibri --prop size=20 --prop color=CADCFC --prop fill=none\n\nofficecli close \"$FILE\"; officecli validate \"$FILE\"\n```\n\n### (b) Multi-element coordinated morph — Actors / Choreography\n\n**Visual outcome.** Three scene actors (`!!scene-ring`, `!!scene-dot`, `!!scene-band`) repositioned across 3 slides to feel like a camera pan. Fresh per-slide titles fade in / out via the `#sN-*` ghost pattern. Use this when the narrative has a continuous visual backdrop.\n\n```bash\n# Slide 1 — anchor composition (already built via recipe a; here we add actors)\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!scene-ring' --prop preset=ellipse \\\n  --prop fill=E94560 --prop opacity=0.3 --prop x=5cm --prop y=3cm --prop width=8cm --prop height=8cm\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!scene-dot' --prop preset=ellipse \\\n  --prop fill=0F3460 --prop x=28cm --prop y=15cm --prop width=1cm --prop height=1cm\n\n# Slide 2 — morph: ring moves + grows, dot slides left (spatial variety ≥ 5cm on both)\nofficecli set \"$FILE\" \"/slide[2]\" --prop transition=morph\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!scene-ring' --prop preset=ellipse \\\n  --prop fill=E94560 --prop opacity=0.6 --prop x=20cm --prop y=2cm --prop width=12cm --prop height=12cm\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!scene-dot' --prop preset=ellipse \\\n  --prop fill=0F3460 --prop x=3cm --prop y=16cm --prop width=1.5cm --prop height=1.5cm\n# Ghost slide-1 content (name path still resolves after morph — see Known Issues)\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=#s1-title]\" --prop x=36cm 2>/dev/null || true\n\n# Verify morph pair: identical names on slides 1 & 2\nofficecli get \"$FILE\" /slide[1] --depth 1 --json | jq -r '.data.results[0].children[]?.format.name // empty'\nofficecli get \"$FILE\" /slide[2] --depth 1 --json | jq -r '.data.results[0].children[]?.format.name // empty'\n# Compare — `!!scene-ring` and `!!scene-dot` MUST appear on both, byte-identical.\n# Note: morph stores names with a `!!` prefix; compare the prefixed forms.\n```\n\n### (c) Continuous multi-slide morph (story arc) — use helpers\n\n**Visual outcome.** A 5-slide arc telling one continuous story: same 2 scene actors drift across the canvas as the narrative progresses; content (`#sN-*`) refreshes per slide and is ghosted on the next. Building this by hand is ~60 commands — use `reference/morph-helpers.py` to keep the build script short and auto-verified.\n\n```python\n#!/usr/bin/env python3\n# Invoke the provided helper library for clone + ghost + verify\nimport subprocess, sys, os\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\nHELPERS = os.path.join(SCRIPT_DIR, \"reference\", \"morph-helpers.py\")\nFILE = \"deck.pptx\"\n\ndef helper(*args):\n    subprocess.run([sys.executable, HELPERS, *[str(a) for a in args]], check=True)\n\n# ... assume slide 1 is built with 2 scene actors (!!scene-ring, !!scene-dot) + #s1-title\n# Helper builds slide 2–5 with: clone from previous + apply transition=morph + ghost previous #sN- content\n# `clone` prints the cloned slide's shape list — read it to pick which shape indices carry the\n# previous slide's #s(n-1)- content, then pass those explicit indices to `ghost`.\nfor n in range(2, 6):\n    helper(\"clone\", FILE, n - 1, n)          # clone + set transition=morph + list shapes (note the #s(n-1)- indices)\n    helper(\"ghost\", FILE, n, 1, 2)           # ghost the #s(n-1)- content shapes by index (here shapes 1 & 2)\n    # …then add THIS slide's #sN- content via officecli add as normal…\nhelper(\"final-check\", FILE)                   # structural pass; DOES NOT catch !! lingering in visible area\n```\n\nHelper signatures and source: `reference/morph-helpers.py` (`clone`, `ghost`, `verify`, `final-check`). The shell equivalent is `reference/morph-helpers.sh` — pick one per platform; do not mix.\n\n**When to use helpers vs raw `officecli`.** For 2-3 slide decks, raw commands (recipes a, b) are clearer. For 5+ slides with repeating clone/ghost/verify cadence, helpers save ~40% of commands and provide built-in verification. Every slide is still closed by `officecli validate` before delivery.\n\n### (d) Morph + fade hybrid — entrance on morph slide\n\n**Visual outcome.** A morph pair where `!!scene-ring` moves continuously while a NEW per-slide card fades in simultaneously. Used when a morph-paired backdrop carries the eye and fresh foreground content needs a softer entrance than a raw appearance.\n\n```bash\n# Slide 2 already has transition=morph and !!scene-ring. Add a new card with fade-entrance.\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=#s2-card' --prop preset=roundRect \\\n  --prop fill=F5F7FA --prop line=none --prop x=2cm --prop y=12cm --prop width=10cm --prop height=5cm\n\n# Apply simultaneous-with-morph fade entrance to the new card.\n# 'fade-entrance-300-with' = fade in, 300ms, trigger=withPrevious (plays with the morph transition).\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=#s2-card]\" --prop animation=fade-entrance-300-with\nofficecli get \"$FILE\" \"/slide[2]/shape[@name=#s2-card]\" --json | jq '.data.results[0].format.animation'  # readback sanity — drops the trigger suffix, reads back as \"fade-entrance-300\"\n```\n\n**Why this works.** Morph animates the `!!scene-*` shapes only (they have a pair on slide 1); the new `#s2-card` has no slide-1 counterpart, so morph would default-fade it — `fade-entrance-300-with` makes that fade explicit and timed. Keep the animation per pptx v2 floor: ≤ 600ms, no bounce / swivel / fly-from-edge (`officecli help pptx animation` for the canonical preset list).\n\n## Choreography — animation types + staggered timing\n\nHow morph animates multiple shapes determines what the audience sees. Pick the right mechanism for each pair:\n\n| Animation type | How to achieve it (between Slide A and Slide B) |\n|---|---|\n| Simple move | Same `!!` name on both slides, same size, different `x`/`y` — morph interpolates position |\n| Scale transform | Same name, different `width`/`height` — morph interpolates size (and re-positions the center) |\n| Move + scale | Different `x`, `y`, `width`, `height` simultaneously — morph handles all dimensions at once |\n| Color / opacity shift | Same name, different `fill` or `opacity` — morph cross-fades the fill |\n| Rotation | Same name, different `rotation` (degrees) — morph rotates along the shortest arc |\n| Font size change | Same name, different `size` (pt) on text shape — interpolates in PowerPoint 365; less reliable on Keynote / WPS / LibreOffice (may degrade to crossfade). For portable motion, pair `size` change with a matching `width`/`height` delta or an `x`/`y` displacement — the spatial change keeps motion visible when size interpolation drops out |\n| Enter (fade in) | Shape exists only on Slide B (no counterpart on A) — morph fades it in |\n| Exit (fade out) | Shape exists only on Slide A (no counterpart on B) — morph fades it out |\n\n**Multi-shape timing constraint.** All `!!` shapes in one morph pair animate **simultaneously** — there is no per-shape delay / duration knob in the CLI (help confirms: no `morph.duration` / `morph.delay` on slide). To stagger shape A before shape B, **split the transition into two pairs** with an intermediate slide:\n\n```\nSlide 2 → Slide 3:  !!actor-A moves (!!actor-B stays put)\nSlide 3 → Slide 4:  !!actor-B moves (!!actor-A stays put or ghosts)\n```\n\nSlide 3 is an explicit intermediate keyframe. Do NOT attempt to fake staggering via timing props on the shape's `animation=` prop — Morph runs before per-shape animations.\n\n**Good-enough variety heuristic (Best Practice — creative flexibility).** For a morph to read as \"motion\", change at least 3 of {x, y, width, height, rotation, fill, opacity} on the dominant paired shape, with displacement ≥ 5cm OR rotation ≥ 15° OR size delta ≥ 30%. One shape × 3 props is a valid creative pattern (focus on one hero element).\n\n**Delivery Gate 5b-morph-2 is stricter.** The gate hard-asserts ≥ 3 DIFFERENT `!!`-prefixed shapes each vary by ≥ 1 of {x, y, width, height, rotation, font-size} across the pair — integrity check for \"is this really a morph or a pretend-morph\". Heuristic informs creative intent; Gate decides delivery. **Brand-constant scenery (pinned header strip, footer bar, logo badge) does NOT count toward the 3-shape quota** — these are supposed to stay put; motion must come from 3 other named shapes. When in doubt, satisfy the stricter Gate.\n\n**Deck-length rhythm.** Filling every transition with morph reads as anxious, not cinematic. Pace morph moments to deck length:\n- **8-10 slides (dense):** 3-5 morph moments; motion can cluster.\n- **12-18 slides (ceremonial):** 3-5 TOTAL morphs, spaced every 4-6 slides; use `transition=morph` at section dividers so the animation reads as chapter punctuation, not continuous agitation.\n- **18+ slides (Act-based):** structure into 3 acts with 1 long section-divider morph between acts (5-10s of deliberate motion with a brief hold), plus 2-3 quieter morphs inside each act. Lean heavier on `!!scene-*` continuity than per-slide `!!actor-*` churn.\n\n## Scene-actor spatial rule\n\nScene actors and actors moving across the canvas MUST stay in predictable zones during morph — otherwise they cross over content and read as clutter.\n\n**Safe zones (prefer for scene actor rest positions and morph paths):**\n\n```\nTop-right corner:   x ≥ 24cm, y ≤ 6cm\nBottom-right:       x ≥ 24cm, y ≥ 12cm\nBottom-left:        x ≤ 2cm,  y ≥ 12cm\nOff-canvas (ghost): x ≥ 33.87cm  (canvas right edge; use x=36cm for explicit ghost)\n```\n\n**Avoid resting actors in the content core:** `x = 2~28cm, y = 3~16cm`. Actors may **pass through** the core during morph (that's the motion), but they should not end a slide parked there with high opacity unless they are content themselves (`!!actor-*` carrying the slide's message).\n\n**Before placing any scene actor, inspect existing shape bounds:**\n\n```bash\nofficecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n  jq -r '.data.results[0].children[]? | \"\\(.format.name // .path)  x=\\(.format.x) y=\\(.format.y) w=\\(.format.width) h=\\(.format.height)\"'\n```\n\nConfirm the actor's target position does not overlap any `#sN-*` content shape's bounding box (`x` to `x + width`, `y` to `y + height`). If it would overlap, lower actor `opacity` ≤ 0.15 OR move it to a safe zone.\n\n## Style library lookup workflow\n\n`reference/styles/` holds 52 visual style directories (dark / light / warm / vivid / bw / mixed moods) — design inspiration, not templates. Use the library as **on-demand reference**, not as a content dump.\n\n**Why lookup, not copy.** Each of the 52 `build.sh` files is a complete style demo — but the coordinates were hand-tuned for that specific demo's content length. Copying them verbatim into a deck with different content produces overlaps and misalignment (flagged in `INDEX.md` L5-11). The library's value is the **design logic**: palette choice for a mood, signature shape, choreography pattern. Apply that logic to your own grid math.\n\n**Four-step lookup:**\n\n1. **Browse INDEX.** `reference/styles/INDEX.md` groups all 52 styles by palette category and mood (e.g. `dark--premium-navy` = authoritative / refined; `warm--earth-organic` = organic / grounded). The Quick Lookup table also shows each style's **primary hex trio** (bg / fg / accent) — if the user specified a brand color, scan the hex column to find the nearest match without opening every `style.md`. Pick 1 style that matches the topic mood OR aligns with the user-specified hex.\n2. **Read philosophy.** Open `reference/styles/<style-id>/style.md` for design intent — type pairing, color logic, signature elements.\n3. **Glance technique.** Open `reference/styles/<style-id>/build.sh` ONLY for technique reference (signature shapes, palette hex codes, choreography ideas) — **coordinates are known-buggy per `INDEX.md` L5-11**; do not copy them.\n4. **Apply on your own canvas.** Build your deck using pptx v2 grid math + visual floor; borrow only the palette and the signature gesture.\n\n**Pointer:** `→ see reference/styles/<style-id>/` — never inline-copy coordinates from a style build.sh.\n\n## Delivery Gate (inherits pptx v2 + morph additions)\n\n**Gate 1–5a: full port from pptx v2.** → see pptx v2 §Delivery Gate. Schema (whitelisting C-P-2 chart spPr), token grep (`$…$` / `{{…}}` / `\\$\\t\\n` / `()` / `[]`), hyperlink rPr (C-P-1), slide-order sanity, dark-on-dark contrast (Gate 5a). **Refuse to declare done until every pptx Gate 1–5a prints its OK message.** Morph decks have the same token / schema / order risks as any pptx.\n\n### Gate 2 morph addendum — price / metric tokens eaten by zsh\n\nPptx v2 Gate 2 covers `$…$`, `{{…}}`, `\\$\\t\\n` literals, empty `()` / `[]`. Morph decks add a class of leaks: price / metric tokens (`$9/mo`, `$29/month`, `$199/yr`) written in double-quoted `--prop text=\"…\"` — the shell eats `$9` as an empty variable and the CLI stores `/mo` or a stray period. Run this in addition to pptx Gate 2:\n\n```bash\n# Gate 2 morph — price / metric token leaks + stray-period placeholders\n# Pattern hits: bare prices ($9, $29, $9.99), /unit suffix ($9/mo, $199/yr), ${VAR}, \\n/\\r/\\t, lone period\nLEAKS=$(officecli view \"$FILE\" text | grep -nE '\\$[0-9]+(\\.[0-9]+)?(/(mo|month|yr|year|day|wk|week|hr|hour))?|\\$\\{[A-Z_]+\\}|\\\\[nrt]|^\\.$' || true)\nif [ -z \"$LEAKS\" ]; then echo \"Gate 2 morph OK\"; else echo \"LEAK: $LEAKS\"; fi\n```\n\nCovers: `$9` `$9.99` `$29/month` `$199/yr` `$1/day` `${VAR}` `\\n`/`\\r`/`\\t` literals + stray `.` placeholders. Fix: single-quote the prop (`--prop text='$9/mo'`).\n\n### Gate 5b — Visual audit via HTML preview (MANDATORY) — extended for morph\n\nRun `officecli view \"$FILE\" html` and Read the returned HTML path. For every slide, answer the pptx v2 Gate 5b questions (overlap / dark-on-dark / divider overlap / order sanity / missing arrowheads) PLUS these four morph-specific checks:\n\n**Important: selectors with prefix match.** `officecli query` only supports operators `=`, `!=`, `~=`, `>=`, `<=`, `>`, `<` — there is NO `^=` prefix operator. A selector like `shape[name^=!!actor-]` returns an `invalid_selector` error. For \"starts-with\" filtering, use a `get --depth 1` loop + `jq startswith()` as shown below.\n\n- **5b-morph-1 — `!!actor-*` leak into visible area after its section ends.** For every `!!actor-*` that should have exited, confirm `x ≥ 33.87cm` (canvas right edge). Loop + filter (selector-safe):\n  ```bash\n  NSLIDES=$(officecli query \"$FILE\" slide --json | jq '.data.results | length')\n  for N in $(seq 1 $NSLIDES); do\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r --arg n \"$N\" '.data.results[0].children[]? |\n        select(.format.name? // \"\" | startswith(\"!!actor-\")) |\n        select((.format.x // \"0cm\" | rtrimstr(\"cm\") | tonumber) < 33.87) |\n        \"slide \\($n) leak: \\(.format.name) stuck at x=\\(.format.x)\"'\n  done\n  ```\n  Any line printed = actor stuck visible. `final-check` misses this — only the loop + Read HTML do.\n\n- **5b-morph-2 — Adjacent slides have identical spatial composition (no motion).** Hard rule: between every morph pair, ≥ 3 DIFFERENT `!!`-prefixed shapes must each differ by ≥ 1 of {x, y, width, height, rotation, font-size}. Proof loop (dump both slides, diff same-name shapes, count differing shapes):\n  ```bash\n  for K in 1 2 3 4; do\n    A=$(officecli get \"$FILE\" \"/slide[$K]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) |\n        \"\\(.format.name)|\\(.format.x)|\\(.format.y)|\\(.format.width)|\\(.format.height)|\\(.format.rotation // 0)\"')\n    B=$(officecli get \"$FILE\" \"/slide[$((K+1))]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) |\n        \"\\(.format.name)|\\(.format.x)|\\(.format.y)|\\(.format.width)|\\(.format.height)|\\(.format.rotation // 0)\"')\n    VARIES=$(diff <(echo \"$A\") <(echo \"$B\") | grep -c '^[<>]')\n    if [ \"$VARIES\" -lt 6 ]; then echo \"pair $K→$((K+1)) FLAT: only $VARIES diff-lines (need ≥ 6 = 3 shapes × 2 sides)\"; fi\n  done\n  ```\n\n- **5b-morph-3 — Morph-pair name mismatches.** Adjacent slides must share at least 2 `!!`-prefixed names exactly. Proof (note: children live at `.data.results[0].children[]` — bare `.data.children[]` returns null):\n  ```bash\n  for N in 1 2 3 4 5; do\n    echo \"--- slide $N ---\"\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) | .format.name'\n  done\n  ```\n  Visually compare sequential blocks — shared `!!` names between N and N+1 are the morph pairs. Zero overlap = the pair is a plain fade.\n\n- **5b-morph-4 — `#sN-*` lingering on slide N+1 (ghost leak).** Per-slide content MUST be ghosted (`x=36cm`) on the NEXT slide. Loop + filter per N≥2:\n  ```bash\n  NSLIDES=$(officecli query \"$FILE\" slide --json | jq '.data.results | length')\n  for N in $(seq 2 $NSLIDES); do\n    PREV=$((N-1))\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r --arg n \"$N\" --arg p \"$PREV\" '.data.results[0].children[]? |\n        select(.format.name? // \"\" | startswith(\"#s\\($p)-\")) |\n        select((.format.x // \"0cm\" | rtrimstr(\"cm\") | tonumber) < 33.87) |\n        \"slide \\($n) leak: \\(.format.name) stuck at x=\\(.format.x)\"'\n  done\n  ```\n  Any line printed = a `#s(N-1)-*` shape stayed visible on slide N. Ghost it.\n\n**REJECT the delivery** if any 5b-morph-1..4 loop prints a line. Collect stdout from all four loops into one stream and enforce with the COUNT pattern: `LEAK_COUNT=$(...all four loops... | wc -l); if [ \"$LEAK_COUNT\" -gt 0 ]; then echo \"REJECT: $LEAK_COUNT morph leaks\"; else echo \"Gate 5b-morph OK\"; fi`.\n\n## Renderer honesty\n\n**Morph renders in:** PowerPoint 365 (Windows/Mac), Keynote, WPS, PowerPoint Online.\n\n**Morph does NOT render in:** LibreOffice Impress (renders static, sometimes as fade), Google Slides web viewer (loses interpolation), most HTML / SVG viewers, `officecli view html` (structural only — morph is runtime). This is `[RENDERER-BUG]`, not a skill defect. Tell the user explicitly: \"Open in PowerPoint 365 / Keynote / WPS to see the morph motion; other viewers will show static or plain fade.\"\n\nStatic screenshots from any renderer **cannot verify morph motion** (the motion only exists at runtime). Use Gate 5b queries above to prove pair correctness; use a live viewer to prove motion quality.\n\n## Ghost Discipline & Actor Lifecycle\n\n**Every `!!actor-*` and `#sN-*` shape must be managed across EVERY slide, not just its \"exit\" slide.**\n\n### The Per-Slide Ghosting Rule\n\nWhen building a multi-slide morph deck:\n1. **Slide N: Introduce `!!actor-ring` (visible at x=0cm)**\n2. **Slide N+1: Add new content. Before finishing, ghost `!!actor-ring` to `x=36cm`.**\n3. **Slide N+2: Add more content. Re-ghost `!!actor-ring` to `x=36cm` again.** (Not optional — even though it was already off-screen, each slide is a fresh canvas.)\n4. **Slide N+3: If `!!actor-ring` should be visible again, move it back to x=0cm or its new position.**\n\n**Why:** Each slide's shape list is independent. Moving a shape off-canvas on slide N does NOT carry over to slide N+1 — if you forget to re-ghost it, it will re-appear at its original position on N+1.\n\n### Workflow Pattern (Bash)\n\n```bash\n# After adding new content shapes to slide $SLIDE:\nfor ACTOR in \"!!actor-ring\" \"!!actor-dot\" \"!!actor-accent-bar\"; do\n  officecli set \"$FILE\" \"/slide[$SLIDE]/shape[@name=$ACTOR]\" --prop x=36cm || true\ndone\n```\n\nOr in a build loop:\n\n```bash\nfor SLIDE_NUM in 3 4 5 6 7 8 9 10 11; do\n  # Add content specific to this slide\n  officecli add \"$FILE\" \"/slide[$SLIDE_NUM]\" --type shape ...\n  \n  # IMMEDIATELY ghost all old actors (M-2 prevention)\n  officecli set \"$FILE\" \"/slide[$SLIDE_NUM]/shape[@name=!!actor-ring]\" --prop x=36cm || true\n  officecli set \"$FILE\" \"/slide[$SLIDE_NUM]/shape[@name=!!actor-dot]\" --prop x=36cm || true\ndone\n```\n\n### Detection: Ghost Count Gate\n\n`morph-helpers.py final-check` counts all shapes at `x ≥ 34cm`. If count > 50, it prints:\n```\nREJECT: Found 135 accumulated ghosts — likely M-2 ghost accumulation.\nRun: officecli query deck.pptx 'shape[x>=34cm]' --json | jq '.data.results | length'\nExpected ≤ 50 (roughly 4–5 active actors × 10–12 slides).\n```\n\n**Fix:** Review the build log, ensure every slide re-ghosts all actors that should not appear in it. Re-run final-check. If still > 50, use `morph-helpers.py clean-accumulation deck.pptx` (see reference section).\n\n## Common Morph Pitfalls (design + workflow traps)\n\nBase pptx pitfalls (shell quoting, zsh `[N]` globbing, hex `#` prefix, `\\n` in prop text) → see pptx v2 §Common Pitfalls. These are the morph-specific traps:\n\n| Pitfall | Correct approach |\n|---|---|\n| `!!scene-card` and `!!actor-card` in the same deck | Names must be unique across prefixes. Rename: `!!scene-card-bg` vs `!!actor-card-content` |\n| Renaming shapes mid-build after some slides are already done | Ghost accumulation bug waiting to happen. Stop, redraw the §Morph Pair Planning table, rerun affected slides |\n| Placing `!!actor-*` into the content core without planning an exit | Every `!!actor-*` needs a ghost slide. Plan it in the pair table BEFORE coding |\n| **Ghost accumulation (M-2): forgetting to re-ghost `!!actor-*` on later slides** | **CRITICAL:** When you add new content to slide N+1, ALL `!!actor-*` from slide N that should not be visible must be moved to `x=36cm` again. Do NOT assume they stay off-screen once ghosted — each slide is independent. Build pattern: `for each new slide: add content shapes → then loop: set each active !!actor-* to x=36cm`. `morph-helpers.py final-check` will REJECT if ghost count exceeds 50. |\n| Forgetting `transition=morph` on a slide | Silent fade. Gate 5b-morph-2 (no motion) catches it; fix via `set /slide[N] --prop transition=morph` |\n| Assuming `@name=` paths break on a morph slide | They do not — `@name=` still resolves after `transition=morph` (M-1); only the *readback* name gains a `!!` prefix |\n| Adjacent slides visually identical | Morph has nothing to interpolate — collapses to plain fade. Apply §Scene-actor spatial rule and move ≥ 3 shapes by ≥ 5cm / ≥ 15° |\n| Trying to stagger 2 shapes via per-shape timing | Not supported — split the pair into two transitions with an intermediate keyframe slide |\n| Testing morph motion in LibreOffice or a browser | `[RENDERER-BUG]`, not skill defect. Test in PowerPoint 365 / Keynote / WPS |\n| Deleting a `!!` shape on exit instead of ghosting it | Deletion breaks morph pairing — the shape vanishes without animation. Always ghost to `x=36cm` |\n| Writing `--prop text=\"$9/mo\"` with double quotes | Shell eats `$9` as empty variable → text stored as `/mo` or stray `.`. Use single quotes: `--prop text='$9/mo'`. Gate 2 morph addendum greps this leak. |\n| Using `<a:br/>` literal inside `--prop text='line1<a:br/>line2'` | Stored as 7 literal characters, not a line break. Use `officecli add \"/slide[N]/shape[@id=K]\" --type paragraph` once per line (M-6). |\n| Using `shape[name^=!!actor-]` selector | `officecli query` has no `^=` operator — returns `invalid_selector`. Use `get /slide[N] --depth 1 --json \\| jq '.data.results[0].children[]? \\| select(.format.name \\| startswith(\"!!actor-\"))'`. |\n\n## Known Issues & Pitfalls\n\nBase pptx bugs C-P-1..7 (hyperlink rPr, chart ChartShapeProperties warning, animation duration readback, animation remove, connector enum, connector `@name=`, chart-color renderer normalization) all apply. **→ see pptx v2 §Known Issues C-P-1..7 for workarounds.**\n\n**Morph-specific (M-1..5):**\n\n| # | Symptom | Workaround |\n|---|---|---|\n| **M-1** | After `officecli set '/slide[N]' --prop transition=morph`, every shape on that slide has `!!` auto-prepended to its name (`#s1-title` → `!!#s1-title`). The readback name is the prefixed form. **Selector filter caveat:** after auto-prefix, `!!#sN-caption` coexists alongside `!!actor-*` — filtering \"scene actors\" in `jq` with `startswith(\"!!\")` produces false matches on auto-prefixed content. Always filter with `startswith(\"!!actor-\")` or `startswith(\"!!scene-\")`, never bare `startswith(\"!!\")`. | `@name=` path selectors **still resolve** — `/slide[N]/shape[@name=#s1-title]` returns the shape (matching is suffix/prefix-tolerant), so no path rewrite is needed. When you need the prefixed name in a jq filter, account for the leading `!!`. |\n| **M-2 🚨** | **Ghost accumulation — `!!actor-*` introduced on slide 3 stays visible on slides 4, 5, 6 unless EXPLICITLY ghosted every page.** `final-check` helper detects this and rejects if ghost count > 50. | **MANDATORY per-slide rule:** After you add new content to a slide, immediately set ALL active `!!actor-*` from previous slides to `x=36cm` (or explicitly position them visible if they belong in the current context). Example: `officecli set /slide[4]/shape[@name=!!actor-ring] --prop x=36cm`. Run after EVERY slide addition, not just at the end. See §Ghost Discipline & Actor Lifecycle below. |\n| **M-3** | Section-transition boundary — on the first slide of a new topic section, previous-section `!!actor-*` shapes visibly linger. No command errors; only visual clutter. | On every section-start slide, explicitly ghost ALL `!!actor-*` from the previous section to `x=36cm`. Scene shapes (`!!scene-*`) stay. |\n| **M-4** | Agents sometimes invent `morph.duration=` / `transition.delay=` as independent props — they are rejected as UNSUPPORTED. | Use the combined shorthand on the `transition` prop: `transition=morph-slow` / `-fast`, or `transition=morph-<DUR_MS>` (e.g. `transition=morph-1500`). Both round-trip on readback (`transitionSpeed=slow`, `transitionDuration=1500`). Only beyond what the shorthand exposes (fine control of the raw XML), fall back to `raw-set` on `<p:transition>` — see M-4 example block below. |\n| **M-5** | `[RENDERER-BUG]` LibreOffice / Google Slides web viewer render morph slides as plain fade (no interpolation). | Test in PowerPoint 365 / Keynote / WPS. Not a skill defect — do not chase. |\n| **M-6** | `<a:br/>` written inside `--prop text='line1<a:br/>line2'` is stored as the literal 7-character string, NOT interpreted as a line break. Audience sees `line1<a:br/>line2` rendered verbatim. | For multi-line bullets / captions, add one paragraph per line: `officecli add \"/slide[N]/shape[@id=K]\" --type paragraph --prop text='line1'` then repeat with `text='line2'`. See pptx v2 §Shell escape for the real-newline workflow. |\n\n**M-4 example — slow down all morph transitions, raw-set fallback** (prefer `transition=morph-slow`; use this only for control beyond the shorthand). Note `//p:transition` matches both `mc:Choice` and `mc:Fallback` on a morph slide, yielding `2 element(s) affected`:\n\n```bash\n# Per-slide: add spd=\"slow\" to every transition element on slide N (2 XML hits per morph slide)\nfor N in 2 3 4; do\n  officecli raw-set \"$FILE\" \"/slide[$N]\" --xpath \"//p:transition\" --action setattr --xml 'spd=slow'\ndone\nofficecli validate \"$FILE\"\n```\n\nReadback: `officecli query \"$FILE\" slide --json | jq '.data.results[].format | select(.transition==\"morph\") | .transitionSpeed'` prints `\"slow\"` for each affected slide.\n\n## Outputs & delivery\n\nEvery morph deck ships with three artifacts, each as a standalone file:\n\n1. `<topic>.pptx` — the deck, closed + `officecli validate` clean (Delivery Gate 1 OK).\n2. `build.sh` or `build.py` — the re-runnable script (bash for shell-native builds; Python for multi-slide arcs using `morph-helpers.py`). Must recreate the deck from a fresh `officecli create` call.\n3. `brief.md` — **standalone file, NOT embedded in anything else.** Contains:\n   - Section 1: topic / audience / purpose / narrative / style direction (1 named style from `reference/styles/INDEX.md`)\n   - Section 2: slide-by-slide outline (page type + one-sentence argument per slide)\n   - Section 3: §Morph Pair Planning table (Pair / Slide A / Slide B / Actors / Ghosts) — the design record the reviewer needs to audit choreography\n\n**Pre-deliver reminder to the user (verbatim-safe wording):**\n\n- \"The deck is ready with morph transitions. Open it in PowerPoint 365 / Keynote / WPS to see the motion — LibreOffice and web viewers render static.\"\n- \"While the build script is running, the `.pptx` may be rewritten several times. If you want to preview progress, use `officecli watch \"$FILE\"` and open the live preview in AionUi — do NOT click 'Open with system app' during the build, or you'll hit a file lock.\"\n\n## Adjustments after creation\n\nStandard adjustments table → see pptx v2 §Common Pitfalls / `swap` / `move` / `remove` / `set`. Morph caveat: **after any `swap` or `move` that reorders morph-paired slides, re-verify the adjacency of shared `!!` names.** Run Gate 5b-morph-3 query above on the affected pairs — if the swap broke a pair, either rename shapes or re-choreograph the transition.\n\n**Final sanity check before delivery.** Run the full Delivery Gate (1 through 5b-morph-1..4), open the `.pptx` in PowerPoint 365 / Keynote / WPS, watch one full slide-to-slide morph to confirm motion is visible. If any Gate prints REJECT, fix and re-run — never deliver with a known-open gate.\n\n## References\n\n- `reference/decision-rules.md` — Pyramid Principle, SCQA, page-type menu, `brief.md` schema. Read during §Morph Pair Planning to decide narrative arc before writing commands.\n- `reference/pptx-design.md` — residual design notes (Scene Actors mechanics, page-type table, choreography patterns). Canvas / fonts / colors live in pptx v2 — this file covers only the morph-unique material.\n- `reference/morph-helpers.py` — Cross-platform (Mac / Windows / Linux) Python helpers for clone + ghost + verify + final-check. Import as a library or call via CLI args. Preferred for 5+ slide arcs.\n- `reference/morph-helpers.sh` — Bash equivalent. Pick one per project; do not mix.\n- `reference/styles/INDEX.md` — 52-style visual library, grouped by palette (dark / light / warm / vivid / bw / mixed) and mood. Lookup workflow in §Style library lookup workflow above.\n- `skills/officecli-pptx/SKILL.md` — base pptx v2 rules (visual floor, grid, canonical palettes, chart-choice, connector canon, Delivery Gate 1–5a, Known Issues C-P-1..7, Shell escape 3-layer).\n","skills/officecli-pitch-deck/SKILL.md":"---\nname: officecli-pitch-deck\ndescription: \"Use this skill when the user is building a fundraising / investor pitch deck — seed, Series A / B / C, convertible note, SAFE round, strategic raise. Trigger on: 'pitch deck', 'investor deck', 'Series A deck', 'Series B deck', 'Series C deck', 'fundraising deck', 'seed pitch', 'VC deck', 'raising capital', 'term sheet presentation'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate). DO NOT invoke for a generic board review, sales deck, all-hands, or product launch — route those to officecli-pptx base.\"\n---\n\n# OfficeCLI Pitch Deck Skill\n\n**This skill is a scene layer on top of `officecli-pptx`.** Every pptx hard rule — visual delivery floor (title ≥ 36pt / body ≥ 18pt / title ≥ 2× body), 12-column grid on 33.87×19.05cm, 4 canonical palettes, chart-choice decision table, connector canon (`shape` / `from` / `to` / `tailEnd=triangle`), shell escape, resident + batch, Delivery Gate 1–5a — is inherited, not re-taught. This file adds only what **fundraising** needs on top: stage diagnosis (A / B / C), 5 赛道 arc templates, 10 key-slide recipes (cover / problem / solution / market / product / model / traction / team / financials / ask), pitch-specific numbers convention, a VC ship-check, and a pitch-specific fresh-eyes Gate 6.\n\nWhen the pptx base rules cover it, the text here says `→ see pptx v2 §X`. Read `skills/officecli-pptx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what a fundraising deck requires, not every command flag.** When a prop name, enum value, or preset is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help pptx                          # All pptx elements\nofficecli help pptx <element>                # Full schema (e.g. chart, shape, connector, picture)\nofficecli help pptx <element> --json         # Machine-readable\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help wins.** Every `--prop X=` in this file has been grep-verified against `officecli help pptx <element>` — if help adds / renames a prop in a later version, trust help.\n\n## Mental Model & Inheritance\n\n**Inherits pptx v2.** You should have read `skills/officecli-pptx/SKILL.md` first. This skill assumes you know how to: add slides + shapes + charts + connectors; address by `@name=` / `@id=`; quote paths; use `batch` heredocs; write `--prop tailEnd=triangle` on every flow connector; and run the 5-gate Delivery Gate. If any of those are unfamiliar, open a pptx v2 session before continuing.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see pptx v2 §Shell & Execution Discipline. Same rules verbatim — quote `[N]` paths, single-quote values containing `$` (including `$35M`, `$1.2B TAM` in a cover or ask slide), never hand-write `\\$ \\t \\n` in executable examples, one command at a time. Examples below use `$FILE` (`FILE=\"deck.pptx\"`).\n\n**Single-quote every shape text containing `$`.** `--prop text=\"Series B · $35M\"` (double quotes) is WRONG — zsh expands `$35M` → empty, deck renders `Series B · M` silently. `--prop text='Series B · $35M'` (single quotes) is right. This is the #1 pitch-deck shell-escape failure mode (`$35M`, `$18M ARR`, `$1.2B TAM` appear on cover/ask/financials/milestones). Gate 2 cannot detect a stripped `$35M` — no residue. Gate 2b catches common strip patterns; single-quoting PREVENTS them.\n\n## What \"pitch deck\" means here (identity)\n\nA pitch deck is a pptx with a **fundraising layer** on top: VC-oriented narrative arc, verifiable metrics, stage-appropriate data density, founder-credibility surface. Slides are consumed at ~3 seconds per slide in a live room — the pptx v2 rule. Pitch decks add a second constraint on top: **every slide carries one investable proposition**. If a slide is \"interesting background\" that doesn't move the ask forward, cut it. VCs will not. The base pptx rules still apply; pitch decks add six deltas:\n\n1. **Stage determines everything.** Series A / B / C each dictates slide count, narrative weight, which metrics are must-haves, and tolerance for unit-econ sophistication. A Series A deck with 6 pages of CAC/LTV math reads as over-packaged; a Series B deck missing unit econ reads as incomplete. Pick the stage first — everything downstream follows.\n2. **Narrative arc beats feature dump.** 10 essential slides in a fixed order: cover → problem → solution → market → product → model → traction → team → financials → ask. Out of order = VCs disengage.\n3. **Numbers are a contract.** TAM/SAM/SOM must be clean three-layer; CAC/LTV must have a payback line; ARR ≠ revenue; Use-of-Funds must be a four-bucket pie. Sloppy numbers = round dies.\n4. **Team slide carries prior companies.** Avatar grid alone reads as a student project. Add prior-company logos / names + one-line role. Without this, first-time founders look exactly like first-time founders.\n5. **Traction chart y-axis starts at 0.** A \"hockey stick\" starting at `y_min = 80% of current` is a visual lie — VCs who have seen 10,000 decks spot it in < 2 seconds.\n6. **The ask is a slide, not a footnote.** `$XX M` hero + four-bucket Use-of-Funds + runway length. \"We're raising some money\" is not an ask.\n\n### Reverse handoff — when to go BACK to pptx base\n\nStay in **pptx v2 base** for board reviews, all-hands, sales decks, product launches, training decks — anything not tied to raising capital. Use **this skill** only when: (a) the user mentions a specific round (seed / Series A / B / C) or a VC meeting, AND (b) the deck needs at least 4 of {problem, traction, team with credentials, Use-of-Funds, stage-appropriate unit econ, financial projections}.\n\nIf the user says \"fundraising deck\" but the context is a corporate BU quarterly ask, that is a board review. Route to pptx v2 Recipe (d) 10-slide blueprint. If the user says \"board review\" but the context is a small company raising a bridge round, route here.\n\n## Series A / B / C stage diagnosis (decision tool)\n\n**Read this before writing a single command.** Pick the row that matches the user's description — everything downstream (slide count, which metrics, which recipes, what the team slide must show) derives from this one call.\n\n| Stage | Revenue band | Team | Slide count | Dominant narrative (weight) | Must-have data | Common red flag |\n|---|---|---|---|---|---|---|\n| **Seed** | $0 – $1M ARR (often pre-rev) | 2 – 8 FTE | 10 – 12 | Problem (30%) + Solution (25%) + Team (15%) + Market (15%) + Traction (15%) | Founder-market fit story; 1 – 2 design-partner / pilot logos; top-down TAM ok | Over-claiming traction (10 customers = \"market proven\") |\n| **Series A** | $1 – $5M ARR | 10 – 25 FTE | 12 – 16 | Problem (20%) + Solution (20%) + **Market \"why now\"** (15%) + Product (15%) + Traction (20%) + Team (10%) | PMF proof (NRR > 110%, low churn), bottom-up TAM/SAM, pipeline / pilots converted | Bottom-up TAM feels fabricated; CAC not yet meaningful but shown anyway |\n| **Series B** | $5 – $30M ARR | 30 – 100 FTE | 18 – 22 | **Traction + Unit econ (30%)** + Market + Product + Team + Financials (ask) | ARR curve starting at 0; NRR, CAC, LTV, payback (< 18 mo ideal); cohort retention; logo wall | No unit-econ slide; CAC payback > 24mo without explanation; Use-of-Funds missing % |\n| **Series C** | $30M+ ARR | 100+ FTE | 20 – 24 | **Financials + Scale + Moat (40%)** + Market expansion + Team depth | Multi-year GAAP, rule-of-40, GM trajectory, international expansion plan, defensibility | No moat slide; revenue growth without margin story; team slide has no prior CEO / CFO |\n| **Bridge / SAFE** | any | any | 8 – 10 | **Specific bridge reason** + runway math + commitments | Prior round context; specific milestone the bridge funds; committed investor amount | Treating a bridge like a Series A — too many slides dilutes the ask |\n\n**Decision procedure.** From one or two user sentences (\"Series B, $18M ARR, 120 customers, $35M raise\"), pick exactly one stage row. All later choices in this skill reference your stage: which 赛道 template to pull, which recipes are mandatory vs optional, and which Delivery Gate 6 checks fire.\n\n**Corner cases.** Bridge rounds & convertibles between A → B are closer to A or B depending on whether the bridge milestone is \"finish PMF\" (A shape) or \"hit unit-econ target\" (B shape). \"Extension\" rounds at the same stage reuse the earlier stage's skeleton and add a one-slide \"progress since last round\" update.\n\n**Non-SaaS stage overrides.** The ARR / unit-econ shape of Series B fits SaaS. For other verticals, substitute revenue band + unit-econ equivalent + Gate 6.3 grep:\n\n| Vertical | Revenue \"band\" at Series B | \"Unit econ\" equivalent | Gate 6.3 substitute |\n|---|---|---|---|\n| **Bio / Clinical-stage** | pre-rev, 20–60 FTE | burn rate + runway to next milestone (IND / Ph1 readout / BLA) | `shape:contains(\"ORR\")` OR `contains(\"Pipeline\")` OR `contains(\"BLA\")` OR `contains(\"runway\")` ≥ 1 |\n| **Deep Tech / Frontier** | pre-rev or early pilot rev | technical milestones + TRL level + benchmark vs SoTA | `shape:contains(\"TRL\")` OR `contains(\"benchmark\")` ≥ 1 |\n| **Marketplace / Network** | GMV $10–100M | take rate + cohort retention + liquidity | `shape:contains(\"GMV\")` + `contains(\"take rate\")` ≥ 1 |\n| **Consumer hardware** | $2–15M revenue (shipped units) | contribution margin + repeat rate + blended CAC | `shape:contains(\"repeat\")` OR `contains(\"contribution\")` ≥ 1 |\n\nSubstitute the analogue grep when running Gate 6.3 on these verticals. False WARN on SaaS CAC/LTV = expected; real concern = vertical-specific analogue present. Bio Series B decks especially: burn + runway-to-milestone IS the \"unit econ\" story.\n\n## 赛道 arc templates (5 families)\n\n5 mainstream verticals. Each one has different slide weights because what VCs require as proof-of-concept differs. Pick the vertical row; the slide skeleton is a copy-able starting point. Slide counts assume the matching stage row above.\n\n### (1) B2B SaaS / Enterprise software\n\nCanonical arc — the template most of VC muscle memory is built on. Series B example (20 slides): cover · TL;DR · problem · problem evidence · solution · product loop · market TAM/SAM/SOM · **unit economics (CAC / LTV / payback / GM)** · ARR trajectory · retention cohort · logo wall · team · competitors · financials 4-year · ask. Must-have: unit-econ slide from Series A onward; logo wall from Series B onward.\n\n### (2) Consumer (B2C app / consumer hardware / D2C)\n\nNarrative-driven. Early-stage decks lean on **product-experience screenshots + founding story + \"why now\"** market timing; lighter on unit econ (which are usually weaker than SaaS). Series A example (14 slides): cover · hook (30-second product demo or 1-line vision) · problem (lived experience) · solution (product shots) · product-experience flow · \"why now\" market window · pre-order / crowdfunding / early-sales evidence · retention / engagement (DAU, D30) · market (top-down ok if bottom-up unreliable) · competitive positioning · founder story + team · press / endorsements · financials · ask. Must-have: product visuals on ≥ 3 slides; \"why now\" slide (window justification); engagement metric not just revenue.\n\n### (3) Deep Tech / Frontier tech (AI foundation models, quantum, climate hardware, robotics)\n\nTechnology credibility is the sell. Pre-revenue deep tech replaces \"traction\" with **technical milestones + defensibility**. Series B example (22 slides): cover · thesis (one-line \"what changes if this works\") · problem (current state of art) · solution (technical approach) · **technology architecture** · benchmarks vs SoTA · pipeline / TRL levels · market (long-tail) · business model · early commercial traction (pilots, LOIs) · IP / patents · team (usually PhD / ex-FAANG-research) · partners · financials · ask. Must-have: benchmark slide; IP slide; team slide dense with PhDs / prior-lab names.\n\n### (4) Marketplace / Network business (two-sided platform, social, commerce)\n\nLiquidity is the metric. Replace \"unit econ\" with **GMV + take rate + cohort retention + supply / demand balance**. Series A example (15 slides): cover · problem (friction in current supply-demand) · solution · product demo (both sides) · network effects diagram · early liquidity (first-week GMV, time-to-match) · cohort retention · geographic / category expansion plan · competitive positioning vs incumbents · take-rate model · team · financials · ask. Must-have: liquidity metric slide; cohort retention chart; network-effect diagram.\n\n### (5) Bio / Life sciences / Healthtech\n\nRegulatory pipeline IS the business. Replace \"product roadmap\" with **clinical pipeline + regulatory path + scientific evidence**. Series B example (22 slides): cover · unmet medical need · scientific rationale (mechanism of action) · preclinical / clinical data (ORR, safety, endpoints) · **pipeline chart** (candidates × stages × dates) · differentiation vs standard of care · IP / exclusivity · regulatory strategy (IND, BTD, fast-track) · market (prevalence × pricing) · commercial strategy (orphan / specialty / biosimilar) · partnerships / collaborations · team (CSO / CMO with prior FDA wins) · financials (burn to next milestone) · ask. Must-have: pipeline chart; clinical data slide; team slide with prior regulatory wins.\n\n**Cross-vertical rule.** You can mix elements across templates, but never drop a must-have from your primary vertical. A SaaS deck missing unit econ, a bio deck missing a pipeline chart, a marketplace deck missing a liquidity metric — each is an instant VC disqualification.\n\n## Slide Patterns (layout canon)\n\nPatterns are **layout geometry**; recipes below are **narrative intent**. A slide picks one pattern for its visual shape (6 canonical ones below) and one recipe for what it argues (cover / problem / traction / ...). Multiple recipes can share one pattern — Problem / Why-Now / Traction-callout all lean on the 3-stat row (C.2). Pick the pattern first, then fill it with recipe content.\n\n**Speaker notes rule.** Every content slide (non-cover, non-closing) MUST carry speaker notes via `officecli add \"$FILE\" /slide[N] --type notes --prop text='…'`. Missing notes = not shippable — inherits pptx v2 §Hard rules (H7). Run `officecli help pptx notes` to confirm prop names before building.\n\n**Pattern reuse discipline.** Never run the same pattern on two consecutive slides — even with different data, two identical geometries in a row read as a template loop. Alternate C.2 with C.4 or C.5b to break rhythm.\n\n**Vertical centering.** When a slide carries fewer elements than the pattern's maximum, nudge y-positions down 2–3cm to center the visual weight. Tables below assume full content.\n\n### C.1 Title / Cover (dark gradient)\n\n3–4 text shapes on a gradient fill. Slide 1 in every deck.\n\n```\n+----------------------------------+\n|                                  |\n|          TITLE (centered)        |\n|          tagline                 |\n|                                  |\n|   round · amount · date          |\n|  ________________________        |  <- thin brand band\n+----------------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 2cm | 5cm | 29.87cm | 4cm | serif bold, ≥ 36pt (44 typical) |\n| Tagline | 2cm | 10cm | 29.87cm | 2cm | sans 18–22pt |\n| Meta (round · $ · date) | 2cm | 13cm | 29.87cm | 1.5cm | sans 12–16pt |\n\n**Use this when** the slide is the first one (Cover recipe 1) — 3-second identity grab. Background is a 180° linear gradient between two dark palette shades (e.g. Professional Navy `1E2761 → 0D1F35`). If the title wraps to 2 lines, **add height (4cm → 5cm), never drop font below 36pt** — sub-36pt on a pitch cover reads as timid regardless of content. Transition: fade.\n\n### C.2 3-Stat callout row\n\nTitle + 3 big-number / label pairs across. The default for Problem / Why-Now / Traction-callout slides.\n\n```\n+----------------------------------+\n|  Title                           |\n|                                  |\n|   73%      12hr      $4.2B       |\n|   label    label     label       |\n|   source   source    source      |\n+----------------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 1.5cm | 1cm | 30.87cm | 3cm | serif bold ≥ 36pt |\n| Stat 1 number | 2cm | 5cm | 9cm | 4cm | serif bold 60–64pt |\n| Stat 1 label | 2cm | 9.5cm | 9cm | 2cm | sans ≥ 16pt (H4 floor) |\n| Stat 2 number / label | 12.5cm | (same) | 9cm | (same) | (same) |\n| Stat 3 number / label | 23cm | (same) | 9cm | (same) | (same) |\n\n**Use this when** you have 2–3 anchoring numbers and the story is \"three facts argue the point\" — Problem, Why-Now, Market-callout, single-row Traction. Labels ≥ 16pt is the H4 floor (sub-label exception); a number without a label reads as bravado, so never drop labels to 12–14pt to fit more text.\n\n### C.3 4-Stat callout row\n\nSame geometry as C.2 but 4 columns. Numbers 60pt, width 7cm each.\n\n```\n+-------------------------------------+\n|  Title                              |\n|                                     |\n|  73%   12hr   $9M   4.2x            |\n|  lbl   lbl    lbl   lbl             |\n+-------------------------------------+\n```\n\n| Element | X positions | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 1.5cm | 1cm | 30.87cm | 3cm | serif bold 36pt |\n| Stat numbers | 1.5 / 9.5 / 17.5 / 25.5cm | 5cm | 7cm | 4cm | serif bold 60pt |\n| Stat labels | (same X) | 9.5cm | 7cm | 2cm | sans ≥ 16pt |\n\n**Use this when** exactly 4 parallel metrics tell the story and 3 feels under-counted. Prefer C.2 if in doubt — 4 always feels tighter than 3, and wrap risk is real.\n\n> **Wrap warning.** At 60pt in 7cm width, dollar patterns with both `$` and `.` fail: `$9.4M` is 5 glyphs but the wide `$` and `.` in a serif bold make it wrap to 2 lines and destroy the callout. Safe dollar shapes at 60pt/7cm: `$9M`, `$96B`, `$4K` (3–4 chars). Non-dollar shapes: `340%`, `4.2x`, `12.3` safe up to 5 chars. Values ≥ 6 chars (`197min`, `3 Days`) will wrap — either (a) drop font to 44–48pt, (b) abbreviate (`197m`, `$9M`), or (c) shift to C.2 (9cm per stat). Single tokens only, no internal spaces.\n\n### C.4 Chart + Context (chart left, stats right)\n\nChart takes left 55%, 2–3 stacked callouts on the right. The default for Traction / Financials / Market-sizing-with-context.\n\n```\n+-------------------------------------+\n|  Title                              |\n|                                     |\n|  +---------------+   +--------+     |\n|  |               |   | Stat 1 |     |\n|  |    chart      |   +--------+     |\n|  |               |   | Stat 2 |     |\n|  +---------------+   +--------+     |\n+-------------------------------------+\n```\n\n| Element | X | Y | Width | Height |\n|---|---|---|---|---|\n| Title | 2cm | 1cm | 29.87cm | 3cm |\n| Chart | 2cm | 4cm | 17cm | 13cm |\n| Stats column | 21cm | 4cm+ | 11cm | 2.5cm number + 1.5cm label (~3.7cm per pair) |\n\nSub-labels ≥ 16pt (H4 floor). For 5 stats stacked, drop number size to 44pt; 6+ stats means pick a different pattern. Post-batch for column/bar charts: `officecli set \"$FILE\" \"/slide[N]/chart[1]\" --prop gap=80` to tighten bar spacing.\n\n**Use this when** one primary chart drives the story and 2–3 numeric anchors reinforce it — Traction (ARR curve + current ARR + YoY + NRR), Financials (4-year column chart + assumption callouts), Market (bar chart + SOM / CAGR / methodology).\n\n### C.5 Icon-in-circle grid (3-row vertical)\n\n3 vertical rows, each = circle icon on the left + title + 1-line description.\n\n```\n+---------------------------------------+\n|  Title                                |\n|                                       |\n|  (o)  Label one                       |\n|       description one                 |\n|                                       |\n|  (o)  Label two                       |\n|       description two                 |\n|                                       |\n|  (o)  Label three                     |\n|       description three               |\n+---------------------------------------+\n```\n\n| Element | X | Y positions | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Icon circle | 2cm | 4.5 / 8.5 / 12.5cm | 2.5cm | 2.5cm | ellipse, accent fill |\n| Label | 5.5cm | (icon Y + 0) | 25cm | 1.2cm | sans bold 18pt |\n| Description | 5.5cm | (icon Y + 1.3cm) | 25cm | 1.8cm | sans ≥ 16pt (H4 floor), muted |\n\n**Use this when** you have 3 short vertical points that benefit from a visual anchor per row — Solution mechanism, Value pillars, Product loop. Choose C.5b (2×2 grid) when items are parallel and you have exactly 4; choose a horizontal 5-across variant when icons should read side-by-side (e.g. 5-step process).\n\n### C.5b 2×2 Feature grid (4 parallel items)\n\n4 rounded cards, 2 columns × 2 rows. Use when you have exactly 4 parallel items (product pillars, service types, feature quadrants).\n\n```\n+-----------------------------+\n|  Title                      |\n|                             |\n|  +---------+  +---------+   |\n|  | (o) T1  |  | (o) T2  |   |\n|  | body    |  | body    |   |\n|  +---------+  +---------+   |\n|  +---------+  +---------+   |\n|  | (o) T3  |  | (o) T4  |   |\n|  | body    |  | body    |   |\n|  +---------+  +---------+   |\n+-----------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Slide title | 2cm | 1cm | 29.87cm | 2.5cm | serif bold 32pt |\n| Card 1 bg (top-left) | 1.5cm | 4cm | 14.5cm | 7cm | roundRect |\n| Card 2 bg (top-right) | 17.5cm | 4cm | 14.5cm | 7cm | roundRect |\n| Card 3 bg (bottom-left) | 1.5cm | 12cm | 14.5cm | 7cm | roundRect |\n| Card 4 bg (bottom-right) | 17.5cm | 12cm | 14.5cm | 7cm | roundRect |\n| Icon ellipse (each card) | card_x + 0.5cm | card_y + 0.5cm | 2cm | 2cm | — |\n| Card title (each) | card_x + 3.2cm | card_y + 0.6cm | 10.5cm | 1.8cm | sans bold 16pt |\n| Card body (each) | card_x + 0.5cm | card_y + 3cm | 13cm | 3.5cm | sans ≥ 16pt (H4 floor) |\n\n**Use this when** you have exactly 4 parallel items and the eye should land on each equally — 4 product pillars, 4 service tiers, 4 stakeholder types. 3 items feel lonely in a 2×2; 5+ items break the grid — go to a 3×2 (see pptx v2 §(d) grid math) or C.5 row pattern.\n\n> **Z-order canon (critical).** Each card's `roundRect` background must be added immediately before that card's icon / title / body shapes in the batch JSON — pptx paints in insertion order, so a background added after its text paints over and hides the text. When building with `officecli batch`, follow the per-card sequence `bg → ellipse → title → body` strictly. Pattern and z-order details → see pptx v2 §Recipe (c) z-order canon; reuse grid math from pptx v2 §(d) for non-2×2 counts.\n\n**Dark-background variant.** Change card fill from `F0F4F8` (light) to a lighter-dark shade like `1A2540` and bump body text to `FFFFFF` / `E8E8E8`. Palette variables (e.g. `$MUTED`) do NOT expand inside single-quoted heredocs — write the literal hex (`64748B`) in the JSON.\n\n---\n\n## Key-slide recipes (10 essentials)\n\nThe 10 slides every pitch deck carries. Each recipe below gives: **visual outcome** (what the slide looks like from 3m away) + **runnable block** (≤ 18 lines) + **QA one-liner**. All recipes inherit pptx v2 palettes, grid math, type hierarchy, and `--prop tailEnd=triangle` on every connector. Recipes reference the Slide Patterns above: Cover reuses C.1; Problem / Why-Now reuse C.2; Traction / Financials reuse C.4; Feature / pillar slides reuse C.5b. `$FILE` is your deck file.\n\n**Long-title wrap rule.** A 36pt+ title that wraps to 2 lines: add `height` (e.g. 2cm → 3.5cm) — never drop the font below 36pt. Titles < 36pt on a pitch deck read as timid regardless of content.\n\n> **Chart `series1.color=` on `add` works** (applies to every chart recipe below). Passing `--prop series1.color=` (or `series2.color=`, …) on chart `add` applies the series color and exits 0. Verify with a readback if you like: `officecli get \"$FILE\" \"/slide[N]/chart[1]/series[1]\" --json | jq '.data.results[0].format.color'`.\n\n### (1) Cover slide — company · tagline · round · date\n\n**Visual outcome.** Dark navy fill, centered 44pt company name, 20pt one-line tagline underneath, small 16pt meta line at the bottom with round + amount + date. Thin brand band at the very bottom (0.5cm high) in the accent color.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=BrandBand \\\n  --prop geometry=rect --prop fill=CADCFC \\\n  --prop x=0cm --prop y=18.5cm --prop width=33.87cm --prop height=0.55cm\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=CoverTitle --prop text=\"Acme DevOps\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=Tagline --prop text=\"Kubernetes observability, built for production at scale\" \\\n  --prop x=2cm --prop y=10.5cm --prop width=29.87cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=20 --prop color=CADCFC --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=CoverMeta --prop text='Series B · $35M · April 2026' \\\n  --prop x=2cm --prop y=15cm --prop width=29.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=16 --prop color=FFFFFF --prop align=center --prop fill=none\n```\n\n**QA.** Cover has 4 discrete elements (brand band + title + tagline + meta). 80%-whitespace covers fail the pptx \"cover ≥ 60% filled\" floor.\n\n**Consumer variant (3-second grab).** Consumer decks (B2C app / hardware / D2C) should add a single dominant motif — hero product shot, oversized company name (60–96pt), or symbolic mark (crescent moon / abstract geometric). Replace the 44pt title with an 80–96pt name + one motif shape (`--type shape --prop geometry=ellipse --prop fill=<accent>` for an abstract mark, or `picture` at ~40% of slide for a product hero). Keep tagline + round + date identical. SaaS / B2B may skip — the typographic-only cover is sufficient.\n\n### (2) Problem slide — industry pain in 1 sentence + 3 data cards\n\n**Visual outcome.** 36pt title stating the pain (not \"The Problem\"). Below, three equal-width data cards across the slide: each a giant number (40pt) + one-line qualifier (16pt) + source footnote (12pt gray).\n\nGrid math for 3 cards, 1.5cm margins, 0.76cm gap: `usable = 33.87 − 3 − 2·0.76 = 29.35`, `col_width = 29.35 / 3 = 9.78cm`. x-positions: `1.5 / 12.04 / 22.58`.\n\n```bash\nSLIDE=2  # second slide, after cover. Adjust from your build order.\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Kubernetes debugging burns 12 engineering hours / incident\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.5cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"PC1\",\"geometry\":\"roundRect\",\"fill\":\"F5F7FA\",\"x\":\"1.5cm\",\"y\":\"5cm\",\"width\":\"9.78cm\",\"height\":\"10cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"73%\",\"x\":\"1.5cm\",\"y\":\"6cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"of incidents take > 1 hour to diagnose\",\"x\":\"1.5cm\",\"y\":\"9.5cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Calibri\",\"size\":\"18\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Source: 2025 DORA Report\",\"x\":\"1.5cm\",\"y\":\"13cm\",\"width\":\"9.78cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"12\",\"italic\":\"true\",\"color\":\"666666\",\"align\":\"center\",\"fill\":\"none\"}}\n]\nEOF\n# Repeat the 4-block pattern at x=12.04cm and x=22.58cm for cards 2 and 3.\n```\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"Source\")'` returns ≥ 3 (every claim carries a source). If zero sources, VCs will not trust a single number.\n\n### (2b) Why Now slide — Consumer / Seed / early A must-have\n\n**Visual outcome.** 3 cards across: each = **trigger headline** (24pt bold) + **data point** (60pt number or date) + **one-line implication** (16pt) + **source footnote** (12pt gray). Reuse Problem grid math (`col=9.78cm`, x = `1.5 / 12.04 / 22.58`). §赛道 Consumer row 2 must-have; Seed / early A in any vertical benefits when \"market window\" IS the thesis.\n\n```bash\nSLIDE=3\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Why now: three converging triggers\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.5cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# Card 1 (x=1.5cm) — trigger / data / implication / source. Repeat at x=12.04cm and x=22.58cm.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"geometry\":\"roundRect\",\"fill\":\"F5F7FA\",\"x\":\"1.5cm\",\"y\":\"5cm\",\"width\":\"9.78cm\",\"height\":\"10cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"BOM cost\",\"x\":\"1.5cm\",\"y\":\"5.5cm\",\"width\":\"9.78cm\",\"height\":\"1.2cm\",\"font\":\"Calibri\",\"size\":\"24\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"−90%\",\"x\":\"1.5cm\",\"y\":\"7cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"B85042\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Wearable BOM fell 90% since 2021; sub-$40 retail now viable\",\"x\":\"1.5cm\",\"y\":\"11cm\",\"width\":\"9.78cm\",\"height\":\"2cm\",\"font\":\"Calibri\",\"size\":\"16\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Source: IDC Wearables Teardown 2025\",\"x\":\"1.5cm\",\"y\":\"13.5cm\",\"width\":\"9.78cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"12\",\"italic\":\"true\",\"color\":\"666666\",\"align\":\"center\",\"fill\":\"none\"}}\n]\nEOF\n# Card 2 pattern: Oura IPO 2024 / +$2.4B valuation / category proven. Card 3: On-device LLM (Llama 3.2) / Q4-24 / privacy moat viable.\n```\n\n**QA.** 3 cards, each with a date/year citation in the source footnote, each card ≤ 30 words. `officecli query \"$FILE\" 'shape:contains(\"2024\")'` + `'shape:contains(\"2025\")'` ≥ 2 combined (timing anchors visible).\n\n### (3) Solution slide — product in one sentence + 3-step \"how it works\"\n\n**Visual outcome.** 36pt title naming the product pattern (not \"Our Solution\"). Below: 3 or 4 rounded boxes horizontally at y=7cm with elbow connectors + triangle arrowheads. Each box = one verb (observe / correlate / resolve). Reuse pptx Recipe (c) flowchart — orchestration, not a new primitive.\n\n```bash\n# Title — \"a product pattern, not a brand slogan\".\n# Good: \"Auto-correlate K8s events across 3 data planes in 90 seconds\"\n# Bad:  \"The future of observability\"\nSLIDE=4\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop name=SolTitle \\\n  --prop text=\"Correlate K8s events across 3 data planes in 90 seconds\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.2cm \\\n  --prop font=Georgia --prop size=32 --prop bold=true --prop color=1E2761 --prop fill=none\n# 3 boxes across: gap = (33.87 − 3 − 3·7) / 2 = 4.93cm; x = 1.5, 13.43, 25.36\n# Connectors + arrowheads: --prop tailEnd=triangle ALWAYS (pptx Known Issues C-P-5..6).\n# Full batch block → see pptx v2 §Creating and Editing (c) 4-step flowchart; swap N from 4 boxes to 3.\n```\n\n**Product-pattern title rule.** The solution title is a verb + differentiated mechanism + metric. \"Observe / Correlate / Resolve\" is generic; VCs read it as any APM vendor. \"Correlate K8s events across 3 data planes in 90 seconds\" is specific; VCs read it as an insight.\n\n**QA.** Count connectors: `officecli query \"$FILE\" 'connector' --json | jq '.data.results | length'` ≥ (step_count − 1). Every connector must have `tailEnd=triangle` — `view annotated` confirms arrowhead direction. Title must be ≤ 12 words (one breath).\n\n### (4) Market slide — TAM / SAM / SOM nested columns\n\n**Visual outcome.** 36pt title \"Market: $X.YB growing Z% CAGR\". Below: three horizontal bars (or three stacked nested rectangles), labeled TAM / SAM / SOM with dollar values + growth rate. Bottom footnote cites **top-down vs bottom-up source** — pick one methodology per deck, don't mix.\n\n```bash\n# Use a pptx column chart with 3 values. Categories = TAM,SAM,SOM. Source annotation is a separate shape.\nSLIDE=5\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"$42B observability market, 18% CAGR\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=bar \\\n  --prop series1.name=\"USD (billions)\" --prop series1.values=\"42,8.4,0.62\" --prop series1.color=1E2761 \\\n  --prop categories=\"TAM,SAM,SOM (5-yr)\" \\\n  --prop x=2cm --prop y=4cm --prop width=22cm --prop height=12cm \\\n  --prop title='Market sizing — bottom-up by enterprise count × ACV'\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='Source: Gartner 2025 APM Magic Quadrant; SAM = 20% of TAM (K8s-first shops); SOM = 7.4% of SAM over 5 years at 18-24% share.' \\\n  --prop x=2cm --prop y=16.5cm --prop width=29.87cm --prop height=2cm \\\n  --prop font=Calibri --prop size=12 --prop italic=true --prop color=666666 --prop fill=none\n```\n\n**QA.** Top-down vs bottom-up MUST be declared in the source footnote. A TAM without methodology reads as fabricated.\n\n### (5) Product slide — screenshot + 3 bullets OR 3-card feature grid\n\n**Visual outcome.** Two layout options: (a) hero product screenshot on the left (60% of slide), 3 one-line feature bullets on the right (each ≥ 18pt body, no bullets under bullets). (b) 3 feature cards with one icon / screenshot thumbnail each. Pick (a) for consumer / app products, (b) for B2B / infrastructure.\n\n```bash\n# (a) screenshot + bullets — consumer pattern\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type picture --prop src=product_hero.png \\\n  --prop x=1cm --prop y=4cm --prop width=18cm --prop height=13cm\nofficecli set \"$FILE\" \"/slide[$SLIDE]/picture[1]\" --prop alt=\"Product UI: dashboard with 12 K8s clusters, live correlation graph\"\n# Right column bullets (each as a separate shape so sizes stay explicit)\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Auto-correlate across 3 data planes\" \\\n  --prop x=20cm --prop y=5cm --prop width=12cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=20 --prop bold=true --prop color=1E2761 --prop fill=none\n# Repeat for bullets 2 and 3 at y=7.5cm / y=10cm.\n```\n\n**QA.** Picture alt text present (`query 'picture:no-alt'` = empty). Bullets each ≥ 18pt. No \"Lorem\"/\"product name here\"/`{{...}}` tokens.\n\n### (6) Business model slide — unit econ or revenue model\n\n**Visual outcome.** Decision tree by vertical:\n- **SaaS / Enterprise (Series A+)** — 4 KPI callouts: CAC / LTV / Payback / GM (reuse pptx Recipe (e)).\n- **Consumer / D2C** — AOV · repeat-purchase rate · contribution margin · blended CAC.\n- **Marketplace** — GMV / take-rate / liquidity metric / cohort retention.\n- **Bio / Deep tech** — revenue model (license / milestone / royalty split) with assumed ranges.\n\nTitle names the dominant metric (e.g. \"LTV:CAC 4.7x · 14-month payback · 78% gross margin\"), not \"Business Model\". Full 4-card batch block → see pptx v2 §(e) KPI callouts.\n\n```bash\n# SaaS pattern: KPI card values + sub-label + gray VC-floor context under each.\n# Card 1 (LTV): big number \"$420K\", sub \"Lifetime value\", context \"floor: ARPU × GM / churn\"\n# Card 2 (CAC): big number \"$90K\",  sub \"Acquisition cost\", context \"fully-loaded S&M spend\"\n# Card 3 (Payback): big number \"14 mo\", sub \"CAC payback\", context \"VC floor: < 18 mo\"\n# Card 4 (GM): big number \"78%\", sub \"Gross margin\", context \"SaaS floor: 70%+\"\n# Grid math for 4 cards across: usable = 33.87 − 3 − 3·0.76 = 28.59, col = 7.15cm\n# → Full batch template → pptx v2 §(e). Adapt card count 3→4 and card width 9.78cm→7.15cm.\n```\n\n**QA.** For Series B+, all four of {CAC, LTV, payback, GM} present: `officecli query \"$FILE\" 'shape:contains(\"CAC\")'` ≥ 1 AND `shape:contains(\"LTV\")'` ≥ 1 AND `shape:contains(\"payback\")'` ≥ 1 AND `shape:contains(\"gross margin\")'` ≥ 1.\n\n### (7) Traction slide — ARR curve that starts at 0\n\n**Visual outcome.** Line chart taking 60% of slide width; ARR on y-axis **starting at 0** (not at 80% of current value — the VC hockey-stick lie). Right-side commentary card: single giant number (current ARR) + growth rate + 2-3 milestones. If Series B+, second row: cohort retention snippet or logo wall.\n\n```bash\nSLIDE=7\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='ARR: $0 → $18M in 24 months' \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=line \\\n  --prop series1.name=ARR --prop series1.values=\"0.2,0.6,1.4,3.2,6.1,11.3,15.8,18.0\" --prop series1.color=1E2761 \\\n  --prop categories=\"Q1-24,Q2-24,Q3-24,Q4-24,Q1-25,Q2-25,Q3-25,Q4-25\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=21cm --prop height=13cm \\\n  --prop title='Quarterly ARR ($M) — y-axis anchored at 0' \\\n  --prop axismin=0\n# Right callout\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop geometry=roundRect --prop fill=1E2761 --prop line=none \\\n  --prop x=23.5cm --prop y=4cm --prop width=8.8cm --prop height=13cm\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$18M' \\\n  --prop x=23.5cm --prop y=5cm --prop width=8.8cm --prop height=3cm \\\n  --prop font=Georgia --prop size=64 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"ARR · +312% YoY · NRR 128%\" \\\n  --prop x=23.5cm --prop y=9cm --prop width=8.8cm --prop height=3cm \\\n  --prop font=Calibri --prop size=18 --prop color=CADCFC --prop align=center --prop fill=none\n```\n\n**`--prop axismin=0` is load-bearing** — without it, pptx auto-scales the y-axis to start near the lowest value. That is the hockey-stick lie. Gate 6 greps this below.\n\n**QA.** ARR curve chart must carry `axismin=0`. `officecli get \"$FILE\" \"/slide[$SLIDE]/chart[1]\" --json | jq .format.axisMin` returns `0` (CLI emits camelCase `axisMin` in readback even though input prop is lowercase `axismin`).\n\n### (8) Team slide — avatars + names + prior companies (not just a wall)\n\n**Visual outcome.** 3- or 4-card row across the middle of the slide. Each card: picture (6×6cm) on top; name (20pt bold); role (16pt); **prior company + title** (16pt italic, 1 key line); optional LinkedIn URL footer (12pt). Team slide with just headshots and names reads as amateur.\n\n```bash\nSLIDE=11\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Team: 3 prior exits, 42 years combined K8s\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# Card 1 — CEO\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type picture --prop src=alice.jpg \\\n  --prop x=2cm --prop y=5cm --prop width=6cm --prop height=6cm\nofficecli set \"$FILE\" \"/slide[$SLIDE]/picture[1]\" --prop alt=\"Alice Chen, CEO — portrait\"\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Alice Chen\" \\\n  --prop x=2cm --prop y=11.5cm --prop width=6cm --prop height=1cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761 --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"CEO\" \\\n  --prop x=2cm --prop y=12.8cm --prop width=6cm --prop height=0.8cm \\\n  --prop font=Calibri --prop size=16 --prop color=333333 --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"ex-Datadog Director (Series C → IPO); led K8s observability GTM $40M → $200M ARR\" \\\n  --prop x=2cm --prop y=13.8cm --prop width=6cm --prop height=2.5cm \\\n  --prop font=Calibri --prop size=14 --prop italic=true --prop color=333333 --prop align=center --prop fill=none\n# Repeat for Card 2 (CTO, x=10cm) and Card 3 (VP Eng, x=18cm) — 3 cards × 5-6 shapes each.\n```\n\nPrior companies carry **credibility density**. VCs read \"ex-Datadog Director + led $40M → $200M\" in 2 seconds; they read \"co-founder, passionate\" in 0 seconds (because they skip it). Advisors, if shown, go in a smaller row below with a single logo each.\n\n**Arrangement helper.** 3 cards: `col=9.78cm, x=1.5/12.04/22.58`. 4 cards: `col=7.15cm, x=1.5/9.41/17.32/25.23`. 5 cards: `col=5.85cm, x=1.5/7.75/14.0/20.25/26.5` (0.4cm gap, tighter). 6+ or asymmetric → 2-row grid (3×2 / 3×3); see pptx v2 §(d) grid math.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"ex-\")'` + `'shape:contains(\"prior\")'` + `'shape:contains(\"former\")'` ≥ 1 per team member. If zero, you have a portfolio, not a team.\n\n### (9) Financials slide — 4-year plan + honest assumptions\n\n**Visual outcome.** Column chart: 4 years × (revenue, gross margin $, EBITDA). Right-side card: 3-bullet assumption panel (ARPU assumption, win-rate assumption, churn assumption). Title names the trajectory (\"$18M → $85M by FY29\"), not \"Financial Projections\".\n\nReuse pptx Recipe (b) chart + commentary. Pitch-specific: ASSUMPTIONS column on the right is **load-bearing** — a 4-year plan without visible assumptions reads as aspirational. VCs will ask what's behind every number anyway; surface it.\n\nLeft 2/3 — slide + title + 3-series column chart:\n\n```bash\nSLIDE=17\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$18M → $85M ARR by FY29' \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=column \\\n  --prop series1.name=\"Revenue ($M)\"  --prop series1.values=\"18,34,58,85\" --prop series1.color=1E2761 \\\n  --prop series2.name=\"Gross Margin ($M)\" --prop series2.values=\"14,26,45,68\" --prop series2.color=CADCFC \\\n  --prop series3.name=\"EBITDA ($M)\"   --prop series3.values=\"-6,-2,8,22\" --prop series3.color=B85042 \\\n  --prop categories=\"FY26,FY27,FY28,FY29\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=20cm --prop height=13cm \\\n  --prop title='4-year plan — revenue, GM, EBITDA ($M)'\n```\n\nRight 1/3 — assumptions commentary card:\n\n```bash\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop geometry=roundRect --prop fill=F5F7FA --prop line=none \\\n  --prop x=22.5cm --prop y=4cm --prop width=9.8cm --prop height=13cm\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Key Assumptions\" \\\n  --prop x=23cm --prop y=4.5cm --prop width=8.8cm --prop height=1.2cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761 --prop fill=none\n# 5 assumption bullets as 5 separate paragraph shapes at y=6, 7.5, 9, 10.5, 12cm — size=14, italic=true.\n# Keep each bullet ≤ 14 words so 8.8cm width fits without wrap.\n```\n\n**Assumptions panel is load-bearing.** A 4-year plan without visible assumptions reads as aspirational. VCs ask what's behind every number anyway — surface the three or four assumptions that drive the curve.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"assumption\")'` OR `'shape:contains(\"Assumes\")'` ≥ 1. If zero, add the panel.\n\n### (10) The Ask — hero number + 4-bucket Use-of-Funds + runway\n\n**Visual outcome.** Dark fill (match cover). Hero number in the center top: `$35M` at 96pt white. Below, a 4-bucket pie OR a 4-card row listing **Engineering 40% / GTM 35% / G&A 15% / Reserve 10%**. Bottom line: \"18-month runway to $40M ARR\" (next milestone, not \"until next round\").\n\n```bash\nSLIDE=20\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$35M Series B' \\\n  --prop x=2cm --prop y=2cm --prop width=29.87cm --prop height=4cm \\\n  --prop font=Georgia --prop size=88 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=pie \\\n  --prop series1.name=\"Use of Funds\" --prop series1.values=\"40,35,15,10\" \\\n  --prop categories=\"Engineering,Go-to-Market,G&A,Reserve\" \\\n  --prop colors=\"CADCFC,B85042,97BC62,FFFFFF\" \\\n  --prop x=6cm --prop y=7cm --prop width=12cm --prop height=10cm \\\n  --prop title=\"Use of Funds — 4 buckets\"\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='18 months runway to $40M ARR and Series C' \\\n  --prop x=2cm --prop y=17cm --prop width=29.87cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=22 --prop color=CADCFC --prop align=center --prop fill=none\n```\n\n**4-bucket convention.** Engineering / GTM / G&A / Reserve is the canonical breakdown. Typical Series A ranges: Eng 40-50%, GTM 30-40%, G&A 10-15%, Reserve 5-10%. Series B shifts 5-10 points from Eng to GTM.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"Use of Funds\")'` ≥ 1. Pie chart present on ask slide. Runway + milestone on ask slide.\n\n### (11) Pipeline chart — Bio / Deep Tech must-have\n\n**Visual outcome.** Horizontal swimlane. Left column = candidate name; 4 stage columns to the right (Preclinical / Ph1 / Ph2 / Ph3 for bio — or TRL1-3 / TRL4-6 / TRL7-8 / TRL9 for deep tech). Each row's bar extends to its current stage; darker fill for later stages. NCT / trial-ID footer below. §赛道 row 5 Bio must-have; SaaS / Consumer skip.\n\nGrid math: usable `= 30.87cm`, candidate col `= 7cm`, stage cols `= (30.87 − 7) / 4 = 5.97cm` each, row height `= 2.3cm`. Stage col x: `8.5 / 14.47 / 20.44 / 26.41`.\n\n```bash\nSLIDE=6\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Pipeline: 3 candidates across Ph1–Ph3\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# 4 stage headers + candidate row 1 (HLX-201 at Ph2, bar width = 3·5.97 = 17.91cm) in one batch.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Preclinical\",\"x\":\"8.5cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 1\",\"x\":\"14.47cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 2\",\"x\":\"20.44cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 3\",\"x\":\"26.41cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"HLX-201 (lead)\",\"x\":\"1.5cm\",\"y\":\"5.5cm\",\"width\":\"7cm\",\"height\":\"1.5cm\",\"font\":\"Calibri\",\"size\":\"18\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"left\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"geometry\":\"roundRect\",\"fill\":\"1E2761\",\"x\":\"8.5cm\",\"y\":\"5.7cm\",\"width\":\"17.91cm\",\"height\":\"1.1cm\",\"line\":\"none\"}}\n]\nEOF\n# Repeat rows 2 & 3 at y=7.8cm / y=10.1cm with bar widths per stage (Ph1=5.97cm, Ph1-Ph2=11.94cm, Ph1-Ph3=17.91cm).\n# NCT footer full-width at y=16.8cm.\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='NCT05021323 (HLX-201, Ph2, n=48) · NCT06142091 (HLX-304, Ph1, n=24) · IND-filed Q1-26 for HLX-412' \\\n  --prop x=1.5cm --prop y=16.8cm --prop width=30.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=12 --prop italic=true --prop color=666666 --prop fill=none\n```\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"NCT\")' --json | jq '.data.results | length'` ≥ 1. Bar colors darken across stages (`CADCFC` preclinical-only, `1E2761` Ph2-reached).\n\n### (12) Competitive comparison table — Series B+ essential\n\n**Visual outcome.** 5–7 rows × 4–6 cols. Column 1 = competitor name (optional logo shape beside); rest = differentiators (speed / price / integrations / margin / coverage). **Last row = your company, fill highlighted** in an accent color (CADCFC / 97BC62); competitor rows gray. Every Series B+ deck needs this (SaaS: Datadog / New Relic / Splunk; Bio: Kite / Novartis / BMS).\n\n```bash\nSLIDE=13\nofficecli add \"$FILE\" / --type slide --prop layout=blank --pro","skills/officecli-xlsx/SKILL.md":"---\nname: officecli-xlsx\ndescription: \"Use this skill any time a .xlsx file is involved -- as input, output, or both. This includes: creating spreadsheets, financial models, dashboards, or trackers; reading, parsing, or extracting data from any .xlsx file; editing, modifying, or updating existing workbooks; working with formulas, charts, pivot tables, or templates; importing CSV/TSV data into Excel format. Trigger whenever the user mentions 'spreadsheet', 'workbook', 'Excel', 'financial model', 'tracker', 'dashboard', or references a .xlsx/.csv filename.\"\n---\n\n# OfficeCLI XLSX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good xlsx looks like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help xlsx                         # List all xlsx elements\nofficecli help xlsx <element>               # Full element schema (e.g. pivottable, chart, cf)\nofficecli help xlsx <verb> <element>        # Verb-scoped (e.g. add chart, set cell)\nofficecli help xlsx <element> --json        # Machine-readable schema\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help is authoritative**.\n\n## Shell & Execution Discipline\n\n**Shell quoting (zsh / bash).** Excel paths contain `[]`, and number formats contain `$`. Both are shell metacharacters. Rules:\n\n- ALWAYS quote element paths: `\"/Sheet1/row[1]\"`, not `/Sheet1/row[1]`.\n- Use **single quotes** for any prop value containing `$`: `numFmt='$#,##0'`.\n- For formulas with cross-sheet `!` references, use `batch` with a `<<'EOF'` heredoc (see Known Issues).\n- `\\n` and `\\t` in a prop value ARE interpreted by the CLI — `\\n` is a real in-cell line break (pair with `--prop wrapText=true`), `\\t` a tab — consistent across xlsx / docx / pptx. Double them (`\\\\n`) for a literal backslash-n (rarely wanted). (`$` is the shell layer above — single-quote it.)\n\n**Incremental execution.** Run commands one at a time and read each exit code. `officecli` mutates the file on every call; a 50-command script that fails at command 3 will cascade silently. One command → check output → continue.\n\n## Requirements for Outputs\n\nBefore reaching for a command, know what a good xlsx looks like. These are the deliverable standards every workbook MUST meet.\n\n### All Excel files\n\n**Zero formula errors.** Every delivered workbook MUST have ZERO `#REF!`, `#DIV/0!`, `#VALUE!`, `#NAME?`, `#N/A`. No exceptions — guard denominators with `IFERROR` or `IF(x=0,...)`.\n\n**Formulas, not hardcoded values.** If a number can be computed from other cells, it is a formula. Hardcoding `5000` where `=SUM(B2:B9)` belongs breaks the contract that the workbook stays live when inputs change. This is the single most important rule in this skill.\n\n**Professional font.** Use one consistent, professional font across the workbook (Arial / Calibri / Times New Roman). Don't mix four fonts because one sheet came from CSV.\n\n**Explicit widths.** There is no auto-fit. Any column the user will read MUST have `width` set — default 8.43 chars clips everything. Sensible starts: labels 20-25, numbers 12-15, dates 12, short codes 8-10.\n\n**Preserve existing templates.** When editing a file that already has a look, match it. Existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY workbook)\n\nBefore you declare done, run `officecli view \"$FILE\" html` and Read the returned HTML path to confirm all of these:\n\n- **No `###` in any cell.** `###` means a column is too narrow for its widest value. Every column the user reads needs an explicit `width`. `###` in a delivered file is unfinished work, never \"a small visual nit\".\n- **No truncated titles.** Sheet titles, section headers, long labels must fit. Widen the column or apply `wrapText=true` on the cell.\n- **No placeholder tokens rendered as data.** `$fy$24`, `{var}`, `<TODO>`, `xxxx` must never appear in a cell, chart title, series name, or legend. These are build-time tokens that escaped replacement.\n- **Pie / doughnut slices have distinct fill colors.** If the slices render same-colored, switch to `bar` / `column` or set `colors=...` explicitly.\n- **No empty trailing pages / empty chart anchors.** `anchor=D2:J18` over empty source cells looks like a broken chart.\n\nIf any of the above fails, STOP and fix before declaring done.\n\n**Print layout.** Any sheet the user may print or send as a board pack needs page setup. Default portrait + no fit-to-page splits wide tables and charts mid-way. Pick the fit mode by sheet shape:\n\n```bash\n# Summary / chart / dashboard sheet (small, ≤ ~40 rows): fit to a single page.\nofficecli set \"$FILE\" \"/Summary\" --prop orientation=landscape --prop fitToPage=true\n# Tall data table (dozens+ rows): fit WIDTH only, let height paginate naturally.\n# fitToPage=true here crushes every row onto one page → unreadable (### dates, 5px rows).\nofficecli set \"$FILE\" \"/Data\" --prop orientation=landscape --prop fitToPage=1x0\n```\n\n`fitToPage=true` == `1x1` == fit both axes to one page — correct only when the sheet is already short. `1x0` = fit 1 page wide, unlimited pages tall. Trigger: sheet holds a chart, or > 8 columns, or the user's ask mentions print / board / investor.\n\n### Financial models only — skip this section if you are building a template, tracker, CSV import, or operational sheet\n\nScope: budgets, forecasts, 3-statement models, valuation, any `$`-heavy analytical workbook. A customer-support tracker or onboarding template does not need this section.\n\n**Color coding — industry standard.** Five core colors used as a language, not decoration. A reviewer should tell what a cell IS by color alone — before reading the formula.\n\n| Color | Role | Example |\n|---|---|---|\n| Blue text `0000FF` | Hardcoded inputs, scenario variables | `font.color=0000FF` |\n| Black text `000000` | ALL formulas and calculations | default |\n| Green text `008000` | Cross-sheet links inside this workbook | `font.color=008000` |\n| Red text `FF0000` | Links to external files / workbooks | `font.color=FF0000` |\n| Yellow fill `FFFF00` | Key assumptions needing review | `fill=FFFF00` |\n\nA reviewer should tell what a cell IS just by its color — before reading the formula. This is a communication contract, not a cosmetic preference.\n\n**Number formatting — standards, not preferences.**\n\n- **Years** are text, not numbers. Format `2026` not `2,026` — use `numFmt=\"@\"` or set `type=string`.\n- **Currency** carries its unit in the header (`Revenue ($mm)`), not in every cell.\n- **Zeros display as `-`**, not `0`. Use `$#,##0;($#,##0);\"-\"`.\n- **Percentages** default to one decimal: `0.0%`.\n- **Negatives use parentheses**: `(1,234)` not `-1,234`.\n- **Valuation multiples** use `0.0x` format (EV/EBITDA, P/E, etc.).\n\n**Assumptions live in cells, not inside formulas.** `=B5*(1+$B$6)` is correct; `=B5*1.05` is a bug. Document each blue hardcoded input with an adjacent source note in the next cell or a cell comment:\n\n```\nSource: Company 10-K, FY2024, Page 45, Revenue Note\nSource: Bloomberg, 2026-05-02, AAPL US Equity\nSource: Management guidance, Q2 2026 earnings call\n```\n\nAny hardcoded number without a source is an undocumented assumption — a reviewer cannot audit it.\n\n## Common Workflow\n\nSix steps. Every non-trivial build follows this shape.\n\n1. **Open/save lifecycle.** Use `officecli open <file>` at the start and `officecli save <file>` at the end to flush to disk — `save` only writes and leaves the resident warm for follow-up edits; reach for `officecli close <file>` only to release the resident on a one-shot handoff. Both are always safe (never error or lose work). For many cells, use `batch`: **≤ 50 ops/block recommended; tested up to 80+ ops per block on pure value-set payloads with zero failures. Cross-sheet formula batches are the exception — run those non-resident, single heredoc (see Known Issues)**. **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (openpyxl/pandas, Excel, a renderer, delivery).\n2. **Create or load.** `officecli create \"$FILE\"` (new) or `officecli view \"$FILE\" outline` (existing — get the lay of the land first).\n3. **Build incrementally.** One command, read the output, continue. After any structural op (new sheet, chart, named range, pivot), run `get` on it to confirm shape before stacking more on top.\n4. **Format.** Column widths, number formats, freeze panes, tab colors, header fills. Formatting is not optional polish — per \"Requirements for Outputs\" it is part of the deliverable.\n5. **Save, then reckon with the cache.** `officecli save <file>` writes to disk. Newly-added formulas ship without cached values; when a human opens the file in a spreadsheet app, the app recalculates and populates them. **But your downstream `INDEX/MATCH`, `SUMPRODUCT`, or any formula that references an upstream formula will cache whatever the upstream cached at write-time — often `0` or a stale value — and that cached lie survives into non-recalculating readers.** After any multi-formula build involving array formulas (`SUMPRODUCT`, `SUMIFS` with dynamic criteria) or cross-sheet chains, **re-touch every downstream cell** (run `set` again with the same formula) so the engine recomputes its cache from the freshly-cached upstream. ⚠️ Re-touch on cross-sheet chains via resident is unreliable (see Batch / resident caveats) — prefer non-resident `set` for the re-touch pass. Then `officecli get` a few downstream cells and eyeball that their `cachedValue=` is plausible. `validate` is safe with a resident open and itself flushes pending edits to disk (same as docx / pptx).\n6. **QA — assume there are problems.** See the QA section. You are not done when your last command exited 0; you are done after one fix-and-verify cycle finds zero new issues.\n\n## Quick Start\n\nMinimal viable xlsx: 3 months of revenue + a total formula + column widths + a currency format. Adapt, don't copy-paste — your file, your data.\n\n```bash\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" /Sheet1/A1 --prop value=Month --prop bold=true\nofficecli set \"$FILE\" /Sheet1/B1 --prop value=Revenue --prop bold=true\nofficecli set \"$FILE\" /Sheet1/A2 --prop value=Jan\nofficecli set \"$FILE\" /Sheet1/A3 --prop value=Feb\nofficecli set \"$FILE\" /Sheet1/A4 --prop value=Mar\nofficecli set \"$FILE\" /Sheet1/B2 --prop value=42000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/B3 --prop value=45000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/B4 --prop value=48000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/A5 --prop value=Total --prop bold=true\nofficecli set \"$FILE\" /Sheet1/B5 --prop formula=\"SUM(B2:B4)\" --prop bold=true --prop numFmt='$#,##0'\nofficecli set \"$FILE\" \"/Sheet1/col[A]\" --prop width=12\nofficecli set \"$FILE\" \"/Sheet1/col[B]\" --prop width=15\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified: `validate` returns `no errors found`, `B5` resolves to `135000`. This is the shape of every build: open → set cells/formulas → format → close → validate.\n\n## CSV / bulk import\n\n**Native `import` command (preferred for CSV/TSV).** Fastest path; loads a CSV into a sheet in one call. `--header` sets AutoFilter + freeze pane on row 1. Widths and `numFmt` still need a follow-up pass (per D-12 in Dashboard skill).\n\n```bash\nofficecli import \"$FILE\" /Sheet1 --file data.csv --header\nofficecli import \"$FILE\" /Sheet1 --file data.tsv --format tsv --header\nofficecli import \"$FILE\" /Sheet1 --stdin --start-cell B2 < data.csv\n```\n\n**Python + batch fallback** — use when you need custom type coercion, formula injection, or the CSV lives inside another data pipeline. Recipe for 600-6000+ cells:\n\n```python\n# gen_batch.py — produces batch chunks of 80 value-set ops each\nimport csv, json\nops = []\nwith open(\"data.csv\") as f:\n    reader = csv.reader(f)\n    for r, row in enumerate(reader, start=1):\n        for c, val in enumerate(row):\n            col = chr(ord('A') + c)\n            ops.append({\"command\":\"set\",\"path\":f\"/Data/{col}{r}\",\n                        \"props\":{\"value\": val}})\nfor i in range(0, len(ops), 80):\n    print(json.dumps(ops[i:i+80]))\n```\n\n```bash\npython gen_batch.py | while IFS= read -r chunk; do\n  printf '%s\\n' \"$chunk\" | officecli batch \"$FILE\"\ndone\n```\n\nOutcome: 648-row retail CSV (6490 cells) loads in ~30s, zero failures. Tune: start at 80 ops/chunk, drop to 40 if any chunk fails. Numeric type inference and formulas come later via targeted `set` — batch in this recipe is pure value injection.\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` first tells you what sheets exist and where the data is; jump into `view` / `get` / `query` only once you know where to look.\n\n**Open the rendered workbook to eyeball your own work.**\n- `officecli view $FILE html` — Read the returned HTML to audit the rendered output. Each sheet is addressable, charts render inline. Catches `###`, placeholder leakage, pivot layout, row-height clipping.\n- `officecli watch $FILE` keeps a live preview running for the human user — they open it at their own discretion. Use when the user wants to watch along; agent self-check uses `view html` above.\nUse `view html` as your **first visual check after a batch of edits** — fix at source. For final visual verification, the user opens the `.xlsx` in their Excel / WPS / Numbers viewer.\n\n**Orient.** Sheets, dimensions, formula counts.\n\n```bash\nofficecli view \"$FILE\" outline\n```\n\n**Extract.** Plain text dump for content QA or LLM context; scope with `--start` / `--end` / `--cols` for big files.\n\n```bash\nofficecli view \"$FILE\" text --start 1 --end 50 --cols A,B,C\n```\n\nOther `view` modes worth knowing: `annotated` (cell values + types/formulas + warnings), `stats` (numeric summaries), `issues` (broken formulas, empty sheets, missing refs).\n\n**Round-trip dump.** `officecli dump \"$FILE\" [path]` serializes the workbook — or one worksheet (`/Sheet1`, `/sheet[N]`) — into a replayable batch JSON; `officecli batch new.xlsx --input dump.json` replays it. Use it to learn from an existing workbook's structure or clone/adapt a template instead of reading raw OOXML. Coverage per `dump --help`; subtree dumps don't carry workbook-level resources (settings, named ranges) — the replay target must already define them.\n\n```bash\nofficecli dump \"$FILE\" -o blueprint.json            # whole workbook\nofficecli dump \"$FILE\" /Sheet1 -o sheet.json        # one worksheet\nofficecli batch new.xlsx --input blueprint.json\n```\n\n**Inspect one element.** Use XPath-style paths. Always quote — shells glob `[N]`.\n\n```bash\nofficecli get \"$FILE\" \"/Sheet1/A1\"            # one cell\nofficecli get \"$FILE\" \"/Sheet1/A1:D10\"        # range\nofficecli get \"$FILE\" \"/Sheet1/chart[1]\"      # chart\nofficecli get \"$FILE\" \"/Sheet1/table[1]\"      # ListObject\nofficecli get \"$FILE\" \"/namedrange[1]\"        # workbook-level named range\n```\n\nAdd `--depth N` to expand children; add `--json` for machine output. Full element list: `officecli help xlsx`.\n\n**Query across the workbook.** CSS-like selectors. Use for systematic checks (formula coverage, error cells, empty headers) rather than hand-walking.\n\n```bash\nofficecli query \"$FILE\" 'cell:has(formula)'       # every formula cell\nofficecli query \"$FILE\" 'cell:contains(\"#REF!\")'  # broken references\nofficecli query \"$FILE\" 'cell[type=Number]'       # typed filter\nofficecli query \"$FILE\" 'Sheet1!B[value!=0]'      # sheet-scoped\n```\n\nOperators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists).\n\n**Merge cells shortcut.** `officecli query $FILE merge` or `mergedrange` — both are aliases for `mergeCell`. Returns every merged range in the workbook without hand-walking `<mergeCell>` entries.\n\n**When the data is big enough that a row-walk is useless**, reach for Excel's own analytical elements:\n\n- Build a **pivot table** with `officecli add` (`--type pivottable`) to group/aggregate without writing 20 SUMIFs. Attach a **slicer** (`--type slicer`) to give the reader a filter UI.\n- Drop a **sparkline** (`--type sparkline`) in a row to show per-row trends — cheaper than one line chart per row and they print inline. `type` is a strict enum: **`line | column | stacked`** (plus aliases `winloss` / `win-loss` → `stacked`). Invalid `type=` values hard-fail — no silent fallback to `line` anymore.\n- Run `officecli help xlsx pivottable`, `officecli help xlsx slicer`, `officecli help xlsx sparkline` for the exact prop names.\n\n## Creating & Editing\n\nNinety percent of a build is cells, formulas, formatting, and one or two charts. The verbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`.\n\n### Cells and formulas\n\nSet a value and its format in one call. Never write `=` at the start of a formula — the CLI strips it.\n\n```bash\nofficecli set \"$FILE\" /Sheet1/B5 --prop formula=\"SUM(B2:B4)\" --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/C5 --prop formula=\"B5/A5\" --prop numFmt=\"0.0%\"\n```\n\nStructural properties (width, height, freeze, tabColor) live on row / col / sheet nodes:\n\n```bash\nofficecli set \"$FILE\" \"/Sheet1/col[A]\" --prop width=20\nofficecli set \"$FILE\" \"/Sheet1/row[1]\" --prop height=22\nofficecli set \"$FILE\" \"/Sheet1\" --prop freeze=A2 --prop tabColor=1F4E79\n```\n\n### Named ranges\n\nPrefer named ranges over `$B$6` in formulas. They self-document (`GrowthRate` beats `$B$6`) and they let you move the assumption cell without breaking formulas. Because `ref` values contain both `!` and `$`, add them through a batch heredoc:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"GrowthRate\",\"ref\":\"Sheet1!$B$6\"}}\n]\nEOF\n```\n\nSee `officecli help xlsx namedrange` for the full schema.\n\n**Batch JSON does NOT accept shell aliases.** Inside batch `props`, always use the full dotted name — `\"font.color\": \"FF0000\"`, `\"font.size\": 14`, never `\"color\": \"FF0000\"` (ambiguous: text vs fill). On a bare cell, even the shell form is rejected: `--prop color=1F4E79` errors with `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. Rule: in any batch JSON or cell prop, write `font.color` / `fill` explicitly. `parent` should be `\"/\"` for workbook-level elements and `\"/SheetName\"` for sheet-scoped; empty string is not equivalent.\n\n### Charts\n\nChart types live under `officecli help xlsx chart` — the enum is long (20+). Pick the right one for the message: column for category comparison, line for time series, pie only when slices are self-evidently proportional, scatter for correlation. Avoid exotic types unless they answer a specific question.\n\n**Three ways to feed chart data. Pick one per chart — mixing them at add-time is a common trap.**\n\n| Form | Shape | When to use |\n|---|---|---|\n| (a) inline `data` | `--prop data=\"Sales:100,200,300\" --prop categories=\"Jan,Feb,Mar\"` | Tiny demo charts, numbers you will not edit. Source of truth lives in the chart XML, not a cell. |\n| (b) 2D `dataRange` | `--prop dataRange=\"Sheet1!A1:B4\"` (first col = categories, first row = header / series name) | Normal case. Must be **2-D** — single column fails with \"Chart requires data\". |\n| (c) dotted per-series | `--prop series1.name=Sales --prop series1.values=\"Sheet1!B2:B4\" --prop series1.categories=\"Sheet1!A2:A4\"` | Multi-series charts where each series points at non-contiguous ranges, or you want explicit series naming. `series1.values` alone (no `categories`) emits a chart with `1,2,3` as the x-axis. |\n\n**The single-column trap.** `dataRange=\"Sheet1!B2:B13\"` looks like \"value column\" but the engine rejects it with `Chart requires data`. Either widen the range to include the category column (`A2:B13`), or switch to form (c) with explicit `series1.categories`.\n\n**Move / resize a chart after create:** `set chart[N] --prop anchor=\"F5:N25\"` (also `--prop x= --prop y= --prop width= --prop height=`). **Series are still immutable** — to add/change a series, `officecli remove` the chart and `officecli add` with the full series list. Note `remove chart[1]` shifts `chart[2] → chart[1]` and re-add **appends at the end** — to preserve chart order, remove all and rebuild in order.\n\n**Anchor sizing.** No auto-fit. A column chart with 5-6 categories + 2 series needs roughly `A5:L22` (12 cols × 18 rows) to show all labels uncut. Narrower and X-axis labels clip; wider and the chart can split across pages on print/export. If in doubt, start narrow, preview via `view html` (Read the returned HTML path), widen in increments. Page layout (below) is the other half of the fix.\n\n**Chart `dataRange` — always prefix with the sheet.** Even when the chart lives on the same sheet, write `dataRange=\"Summary!A17:C22\"`, not `A17:C22`. The sheet-less form works inconsistently; the prefixed form is 100% reliable.\n\nofficecli adds extended chart types the classic Excel object model lacks: `boxWhisker`, `waterfall`, `funnel`, `histogram`, `treemap`, `sunburst`, `pareto`. Use them when the data calls for them.\n\n**NEVER put unreplaced template tokens in chart title / series name / legend / axis title.** `$fy$24`, `{var}`, `<TODO>`, `$VAR`, `{{placeholder}}` render **literally** in the legend — validate passes, but a CFO sees `$fy$24` where \"FY2024\" should be. Always bind to final text or a cell reference (`title=\"FY2024 Revenue\"` or `series1.name=\"Sheet1!A1\"`).\n\n### Conditional formatting\n\nThree common flavors, each with its own prop shape (consult `officecli help xlsx cf`):\n\n- **Color scales**: cells shaded on a gradient by value — `type=colorscale` with `minColor` / `midColor` / `maxColor`.\n- **Data bars**: in-cell bars showing magnitude — `type=databar`. Set explicit `min` / `max` for consistent scaling across a column; defaults are valid if you omit them.\n- **Formula rules** (the `formulacf` element): highlight row when a condition is true — `type=formula` with `formula=\"$C2>1000\"` and a fill/font.\n\nRule: apply CF sparingly. A workbook where every cell is colored tells the reader nothing.\n\n### Data validation\n\nInput cells in trackers and templates MUST carry data validation. It's cheap and it stops entire classes of downstream bugs. **Three list-source patterns** — pick based on where the allowed values live.\n\n**(a) Inline list** — allowed values are short and fixed in the rule itself.\n\n```bash\nofficecli add \"$FILE\" /Sheet1 --type validation \\\n  --prop sqref=\"C2:C100\" --prop type=list \\\n  --prop formula1=\"Yes,No,Maybe\" \\\n  --prop showError=true --prop errorTitle=\"Invalid\" --prop error=\"Select from list\"\n```\n\n**(b) Named range (preferred for cross-sheet lookups)** — allowed values live in another sheet and may grow. Define the named range first, then reference it. Use a batch heredoc because `ref` contains `!` and `$`:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"StatusList\",\"ref\":\"Lookups!$A$2:$A$4\"}},\n  {\"command\":\"add\",\"parent\":\"/Sheet1\",\"type\":\"validation\",\"props\":{\"sqref\":\"B2:B100\",\"type\":\"list\",\"formula1\":\"=StatusList\"}}\n]\nEOF\n```\n\n**(c) Direct cross-sheet range** — no named range, raw `Lookups!$A$2:$A$4` inside `formula1`. Also needs a batch heredoc to keep `!` and `$` intact:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/Sheet1\",\"type\":\"validation\",\"props\":{\"sqref\":\"C2:C100\",\"type\":\"list\",\"formula1\":\"Lookups!$A$2:$A$4\"}}\n]\nEOF\n```\n\nIf you write the cross-sheet variant as `--prop formula1=...` on the shell, the `!` gets shell-mangled into `\\!` and the dropdown will silently fall back to no list. Verify with `officecli get \"$FILE\" /Sheet1/validation[N]` — `formula1=` must show a plain `!`, no backslash.\n\nOther common `type` values: `decimal`, `whole`, `date`, `textLength`, `custom`. See `officecli help xlsx validation` for operators and the full prop list.\n\n### Other elements (one-liners)\n\n- **Tables** (ListObjects) — `add --type table` with a range; gives auto-filter + structured refs. `officecli help xlsx table`.\n- **Comments** — `add --type comment`; use for documenting hardcoded assumptions. `officecli help xlsx comment`.\n- **Sheet reordering** — `officecli move`, not `swap`. `swap` only works on row/cell paths.\n- **New row/column with the neighbours' formatting** — `add \"$FILE\" /Sheet1 --from \"/Sheet1/row[5]\" --index 5` clones fills, fonts and formulas (refs shift); `add --type row` inserts a bare row.\n\n## Chart Axis-by-Role\n\nEditing a chart axis in place is cheaper than rebuilding the chart. Address axes by **role** (`value` = Y, `category` = X), not by index — the XML order isn't stable.\n\n```bash\nofficecli get \"$FILE\" \"/Sheet1/chart[1]/axis[@role=value]\"\nofficecli set \"$FILE\" \"/Sheet1/chart[1]/axis[@role=value]\" --prop min=0 --prop max=100000\nofficecli set \"$FILE\" \"/Sheet1/chart[1]/axis[@role=category]\" --prop title=\"Month\"\n```\n\nSafe props: `title`, `min`, `max`, `majorGridlines`, `visible`, `labelRotation`.\n\n## QA (Required)\n\n**Assume there are problems. Your job is to find them.**\n\nYour first workbook is almost never correct. Treat QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you were not looking hard enough. The formulas look fine **until** you check two of them against source cells.\n\n### Minimum cycle before \"done\"\n\n1. `officecli view \"$FILE\" issues` — empty sheets, broken formulas, missing refs.\n2. `officecli view \"$FILE\" annotated` (sample ranges) — values + types + warnings.\n3. For every Excel error type, query it:\n   ```bash\n   officecli query \"$FILE\" 'cell:contains(\"#REF!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#DIV/0!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#VALUE!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#NAME?\")'\n   officecli query \"$FILE\" 'cell:contains(\"#N/A\")'\n   ```\n4. `officecli validate \"$FILE\"` — safe with a resident open; `validate` flushes pending edits to disk itself.\n5. **Visual pass — walk every sheet via the HTML preview.** Run `officecli view \"$FILE\" html` and Read the returned HTML path. Each sheet renders with charts inline. Scan for `###`, truncated titles, placeholder tokens (`$fy$24`, `{var}`, `<TODO>`), sliced charts, white-slice pie charts, empty chart anchors — **STOP and fix before declaring done**. \"validate pass\" is not delivery; \"the preview looks like a real workbook\" is delivery. For human preview, run `officecli watch \"$FILE\"` (user opens the live preview at their own discretion) or have them open the `.xlsx` directly in Excel / WPS / Numbers.\n6. **Print layout fix (wide tables / multi-chart sheets).** When a sheet holds a chart or a wide table and the user will print it, set per-sheet page layout — but match the fit mode to the sheet's height:\n   ```bash\n   # Short summary / chart sheet → fit to one page.\n   officecli set \"$FILE\" \"/Summary\" --prop orientation=landscape --prop fitToPage=true\n   # Tall data table → fit width only (fitToPage=true would crush all rows onto one unreadable page).\n   officecli set \"$FILE\" \"/Data\" --prop orientation=landscape --prop fitToPage=1x0\n   ```\n   Outcome: charts/wide tables print without mid-chart splits; tall tables stay readable across natural page breaks. Apply to every sheet that holds a chart or a > 8-column table.\n7. If anything failed, fix, then **rerun the full cycle**. One fix commonly creates another problem.\n\n`officecli view issues` + `view html` are the structural QA pair: `issues` catches broken formulas and empty sheets; `view html` (Read the returned HTML path) catches `###`, truncation, and token leakage. Chart fill colors / theme tints can vary across viewers — spot-check in the user's target viewer when color fidelity matters.\n\n### Formula verification checklist\n\n- [ ] Pick 2-3 formulas at random. Run `officecli get` on each. Confirm the formula string is what you intended **and** `cachedValue=` is what you expect — arithmetic in your head.\n- [ ] **Cached value sanity on every summary cell.** Any cell that aggregates (COUNTA / COUNTIF / SUMPRODUCT / INDEX&MATCH) must have a plausible `cachedValue`. If a progress tracker shows `199 / 199 / 100%` on a blank template, the cache is lying — re-touch the formula via `set` (forces recompute) or manually set a correct cached value. Do NOT ship \"validate passes but the numbers are fiction\".\n- [ ] **Spot-check one cell per numeric column.** `%` columns showing integer `0.0%` throughout means the denominator is wrong or the numerator is cached stale — investigate one cell, fix the pattern.\n- [ ] Ranges include every row: off-by-one on `SUM(B2:B12)` when data goes to `B13` is the most common bug.\n- [ ] Cross-sheet formulas (`Sheet1!A1`) contain no `\\!`. If `officecli get` shows `Sheet1\\!A1`, the `!` was shell-corrupted — delete and re-enter via batch/heredoc.\n- [ ] Named ranges (`officecli get \"$FILE\" \"/namedrange[1]\"`) point at what their names claim.\n- [ ] Every `/` denominator is guarded — `IFERROR(x/y, 0)` or `IF(y=0, 0, x/y)`.\n- [ ] Chart data vs source cells: for every chart with inline data, spot-check data points against `officecli get` of the source cells.\n- [ ] Chart title / series name / legend contain **no** unreplaced tokens (`$...$`, `{var}`, `<TODO>`). Grep the chart via `officecli get /Sheet1/chart[N]`.\n\n### Template QA\n\nWhen editing a template, check for leftover placeholders — they look like content and slip past `validate`:\n\n```bash\nofficecli query \"$FILE\" 'cell:contains(\"{{\")'\nofficecli query \"$FILE\" 'cell:contains(\"xxxx\")'\nofficecli query \"$FILE\" 'cell:contains(\"TBD\")'\n```\n\n### Fresh eyes\n\nWhen you finish a workbook, open it fresh. Read `view text` / HTML preview top-to-bottom as if you are a new reviewer — look for formulas, numbers that look off, formatting inconsistency, missing data.\n\n### Honest limit\n\n`validate` catches schema errors, not design errors. A workbook can pass `validate` with every number wrong. The checklist above — especially spot-checking formulas against source cells — is how you catch what validation can't.\n\n## Known Issues & Pitfalls\n\n### The cross-sheet `!` trap (short)\n\nShells (bash history expansion, zsh splitting) and CLI arg parsing mangle `!` in `Sheet1!A1` into `\\!`. A formula containing `\\!` is silently broken — it renders as literal text and references nothing.\n\n**Fix.** Use a batch heredoc with single-quoted delimiter (`<<'EOF'`), which disables all shell expansion:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[{\"command\":\"set\",\"path\":\"/Summary/B2\",\"props\":{\"formula\":\"Revenue!B13\"}}]\nEOF\n```\n\n**Verify.** After writing, `officecli get` the cell; `formula=` must show a plain `!` with no backslash.\n\n### CLI bug backlog (short)\n\nCLI constraints and gaps to work around — not defects in the output file.\n\n- **Chart series are immutable after create** — to add/change a series: `remove` + `add` with the full series list. (Position is mutable: `set chart[N] --prop anchor=` / `x/y/width/height`.) `remove chart[N]` shifts subsequent indices down; re-add appends at end.\n- **Cross-sheet formula batches run fine through a resident** — a prior \"deadlocks even at 3-5 ops\" caution no longer reproduces. Pure value-set batches stay reliable at 50-80+ ops too. If you ever hit a hang, fall back to a non-resident one-big-batch or individual `set`. **Multiple resident processes on the same file/machine can still contend** — expect non-deterministic hangs if another agent/session holds a resident on the same file.\n- **Conditional formatting naming asymmetry** — the element name for `--type` is `conditionalformatting`; the path suffix is `/cf[N]`. Use `officecli help xlsx conditionalformatting` for schema, `/cf[N]` for paths.\n- **Sheet `position` prop on add** — help says Add processes `position`, but the prop is often ignored. Reorder with `officecli move --index` / `--after` / `--before` after creating the sheet.\n- **`remove /sheet[N]` cascade guard** — rejects sheet remove/rename when the sheet is referenced by validation / conditional format / sparkline / hyperlink / named range on another sheet. Remove those dependent elements first, then remove the sheet.\n- **Batch JSON rejects cell `color` alias** — inside batch `props`, `\"color\": \"FF0000\"` errors `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. The CLI at shell level accepts `--prop color=...` / `--prop size=14` as aliases on non-cell elements, but inside batch JSON on a cell always write the full dotted name: `\"font.color\"`, `\"font.size\"`, `\"font.name\"`.\n\n### Renderer caveats (cross-viewer color fidelity)\n\n`officecli view html` is the right tool for structural QA (overflow, truncation, placeholder leakage, layout) — Read the returned HTML path. Some chart rendering details vary across the viewer the end user opens the file in. Observed divergences:\n\n- **Pie / doughnut fill colors may collapse to a single theme tint** in some viewers (slices look \"all white\" or \"all one color\"). The file may be fine in the user's target viewer.\n- **Line chart / column chart series colors may drift** from the workbook theme in some viewers.\n- **Form-control checkboxes may render as double-boxed** in some viewers.\n\nBefore calling a color or chart \"broken\", open the file in the user's actual target viewer. If it looks correct there, the problem is viewer rendering, not data — do not chase it. The CLI's structural checks (`###`, truncation, placeholder text, layout) remain authoritative.\n\n### Escape layers (shell quoting is above; these are the extras)\n\n`$` is the shell layer (single-quote it, above). `\\n` / `\\t` in a prop value ARE interpreted by the CLI into a real newline / tab. Two more layers:\n\n- **JSON level (batch).** Standard JSON escapes — `\"\\n\"`, `\"\\t\"`, `\"\\\"\"`. A real backslash in the final string is `\"\\\\\\\\\"`.\n- **Excel level.** `\\n` in a cell is a real line break — pair with `--prop wrapText=true` so Excel shows the wrap. Works in a shell-quoted prop directly (`--prop value='a\\nb'`); `\"\\n\"` inside batch JSON gives the same. When in doubt, `officecli get` the cell and compare character-for-character.\n\n### Other common pitfalls\n\n| Pitfall | Fix |\n|---|---|\n| `--name \"foo\"` | All attrs go through `--prop`: `--prop name=\"foo\"` |\n| Guessing a prop name | `officecli help xlsx <element>` — don't improvise |\n| `--prop color=...` on a cell | Ambiguous — use `font.color` (text) or `fill` (bg). Also applies inside batch JSON: always use full dotted names, never shell aliases |\n| `#FF0000` hex colors | Drop the `#`: `FF0000` |\n| `--index` vs `[N]` | `--index` is 0-based (array); `[N]` paths are 1-based (XPath) |\n| Unquoted `[N]` in zsh/bash | Quote every path: `\"/Sheet1/row[1]\"` |\n| Sheet name with spaces | Quote full path: `\"/My Sheet/A1\"` |\n| Year showing as `2,026` | `--prop type=string` or `numFmt=\"@\"` |\n| Modifying a file open in Excel | Close it in Excel first |\n| `swap` not reordering sheets | `swap` is for rows/cells. Use `move --after` / `--before` / `--index` for sheets |\n| Cached values missing after write | New formulas get cached values when a human opens the file; `validate` accepts them either way |\n","skills/officecli-financial-model/SKILL.md":"---\nname: officecli-financial-model\ndescription: \"Use this skill when the user wants to build a financial model — 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising projections — in Excel. Trigger on: 'financial model', '3-statement model', 'P&L + BS + CF', 'DCF', 'WACC', 'NPV', 'terminal value', 'LBO', 'debt schedule', 'cash sweep', 'MOIC', 'IRR / XIRR', 'sensitivity table', 'scenario analysis', 'ARR model', 'unit economics', 'CAC / LTV', 'cap table forecast'. Output is a single formula-driven .xlsx. This skill is a scene layer on top of officecli-xlsx — it inherits every xlsx v2 rule (4-color code, visual floor, number formats, cache-drift, Known Issues, Delivery Gate minimum cycle). DO NOT invoke for a simple budget tracker, CSV dump, or operational KPI sheet — route those to officecli-xlsx base.\"\n---\n\n# OfficeCLI Financial-Model Skill\n\n**This skill is a scene layer on top of `officecli-xlsx`.** Every xlsx hard rule — shell quoting, incremental execution, Help-First Rule, visual delivery floor, CFO 4-color code (blue input / black formula / green cross-sheet / yellow-fill assumption), number-format standards (years as text, zero as `-`, `%` one decimal, negatives in parens), assumption-cell discipline, CSV batch import, chart data-feed forms (a/b/c), the 5-gate Delivery cycle, cache-drift guidance, Known Issues (the cross-sheet `!` trap, batch + resident for formulas, renderer caveats) — is **inherited, not re-taught**. This file adds only what a **financial model** requires on top: three-zone architecture, 3 model-type recipes (3-statement / DCF / LBO), sensitivity + scenario protocols, financial-function patterns, circular-reference discipline, and model-specific Delivery Gates 4–6.\n\nWhen the xlsx base rules cover it, the text here says `→ see xlsx v2 §X`. Read `skills/officecli-xlsx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Help-First Rule\n\nThis skill teaches what a financial model requires, not every CLI flag. When a prop name / alias / enum is uncertain, consult help BEFORE guessing: `officecli help xlsx [element] [--json]`. Help is authoritative for the installed version — when this skill and help disagree, **help wins**. Every `--prop X=` below was verified against `officecli help xlsx <element>`.\n\n## Mental Model & Inheritance\n\n**Inherits xlsx v2.** Read `skills/officecli-xlsx/SKILL.md` first. This skill assumes you know `create` / `open` / `close`, `set` values/formulas, `batch` heredocs for cross-sheet formulas, `/SheetName/A1` paths, named ranges, the 5-gate Delivery cycle, the cross-sheet `!` trap, and that **cross-sheet formulas go non-resident (single batch OR individual `set`), never batch-while-resident**.\n\n## Shell & Execution Discipline\n\nShell quoting, incremental execution, `$FILE` convention → see xlsx v2 §Shell & Execution Discipline. Same rules: quote every `[N]` path, single-quote any prop containing `$` (every number format here — `$#,##0;($#,##0);\"-\"` — needs single quotes), no hand-written `\\$`/`\\t`/`\\n`, one command at a time. Examples below use `$FILE` (`FILE=\"model.xlsx\"`).\n\n## Core Principles (identity)\n\nA financial model is an xlsx with a **decision-grade, formula-driven layer**: every output traces an unbroken chain to blue-font assumptions, every statement balances every period, every valuation is re-auditable. Eight deltas on top of a general xlsx:\n\n1. **Three-zone architecture mandatory:** Inputs → Calc → Outputs. Collapsing zones → unauditable.\n2. **Assumptions live in cells, never inside formulas.** `=B5*(1+Assumptions!GrowthRate)`, never `=B5*1.05`.\n3. **Statements balance every period.** `Assets − Liab − Equity = 0`, `CF.EndingCash = BS.Cash`. Gate 4 fails on `IMBALANCED`.\n4. **Hardcodes audited.** Calc sheets carry zero hardcoded numbers; Gate 6 counts.\n5. **Sensitivity / scenario is first-class.** 2-axis grid, dropdown `INDEX/MATCH` switch, or Base/Upside/Downside cols. Excel Data Tables not reliably supported — manual grids only.\n6. **Cached values on valuation cells load-bearing.** A valuation cell that ships with no cached result (or the `#OCLI_NOTEVAL!` sentinel) sends a blank/wrong number to non-recalculating readers. The evaluator now computes NPV / XNPV / IRR / XIRR; verify the cached value with a readback. Gate 5 spot-checks.\n7. **Circularity is a design choice.** Legitimate rings (interest ↔ cash, revolver plug ↔ ending cash) use `calc.iterate=true`. Accidental circularity is broken algebra — never papered with `iterate`.\n8. **Named ranges for ≥ 3-use assumptions.** `WACC`, `TaxRate`, `TerminalGrowth`, `ExitMultiple`, `ChurnRate`. Declared-unused names are dead decoration — Gate 6 flags.\n\n### Reverse handoff — when to go BACK to xlsx base\n\nStay in **xlsx base** for: budget trackers, CSV-to-report dumps, operational KPI sheets, simple templates, cap tables without forecast logic. Use **this skill** only when the ask mentions: 3-statement / DCF / WACC / NPV / TV / LBO / debt schedule / MOIC / IRR / unit economics / ARR roll-forward / sensitivity grid / scenario switch / pro forma.\n\n## Three-zone architecture (hard rule)\n\nEvery model in this skill builds on three zones. **Name them, tab-color them, and enforce them with executable audits.** Breaking the zone rule is the single most common cause of an unauditable model.\n\n| Zone | Sheet names (convention) | Tab color | Content | Hardcodes | Formulas |\n|---|---|---|---|---|---|\n| **Inputs** | `Assumptions`, `Inputs`, `Drivers` | Yellow `FFC000` | Raw drivers: growth rates, margins, tax, WACC, FTE, pricing, working-capital days | Blue `0000FF` on every cell | Allowed only for derived assumptions (e.g. `=MonthlyARPU*12`) |\n| **Calc** | `P&L`, `Balance Sheet`, `Cash Flow`, `DCF`, `Debt`, `ARR` | Blue `4472C4` | All derivations and statements | **Zero** (enforced by Gate 6) | Black `000000` for same-sheet, green `008000` for cross-sheet |\n| **Outputs** | `Summary`, `Dashboard`, `Sensitivity`, `Returns` | Green `70AD47` | KPIs, sensitivity grids, charts, returns waterfall | Only for labels (non-numeric); Gate 6 counts numeric hardcodes → 0 | Black / green per above |\n\n**Build order is cross-zone-aware.** Assumptions first, then Calc bottom-up on the dependency chain (`IS → BS → CF` for 3-statement; `FCF → WACC → NPV` for DCF), then Outputs last. Building Outputs first caches `0` everywhere and downstream inherits zeros.\n\n**Executable zone audit** (run before Gate 4):\n\n```bash\n# Calc zone: zero numeric hardcodes allowed. `cell:not(:has(formula))` selects the literal (non-formula) cells; `cell:has(formula)` selects the formula cells.\nHARDCODE=$(officecli query \"$FILE\" 'cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null) | select(.path | test(\"/(P&L|Balance Sheet|Cash Flow|DCF|Debt|ARR)/\"))] | length')\n[ \"$HARDCODE\" -eq 0 ] && echo \"Zone audit OK\" || { echo \"REJECT: $HARDCODE hardcoded numeric cells on Calc sheets — move to Assumptions\"; exit 1; }\n# Assumptions zone: should be non-zero.\nINPUTS=$(officecli query \"$FILE\" '/Assumptions/cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null)] | length')\n[ \"$INPUTS\" -ge 5 ] && echo \"Assumptions has $INPUTS hardcoded drivers\" || echo \"WARN: Assumptions has only $INPUTS inputs\"\n```\n\n## Print delivery (board / IC / LP)\n\nWhen the ask contains \"print\" / \"一页\" / \"董事会\" / \"投资人\" / \"IC memo\" / \"LP update\", the print pipeline must emit **only** the Outputs zone. Two artefacts:\n\n```bash\n# 1. Print_Area scoped to the Outputs sheet (Summary or Dashboard).\nofficecli add \"$FILE\" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Summary --prop 'refersTo=Summary!$A$1:$H$40'\n# 2. Hide every non-Outputs sheet — Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.\nfor S in Assumptions 'P&L' 'Balance Sheet' 'Cash Flow' DCF WACC Debt FCF 'S&U' Exit Returns; do\n  officecli raw-set \"$FILE\" /workbook --xpath \"//x:sheet[@name='$S']\" --action setattr --xml \"state=hidden\" || true\ndone\n# 3. fit-to-page landscape on Outputs sheet.\nofficecli raw-set \"$FILE\" /Summary --xpath \"//x:worksheet\" --action prepend --xml '<sheetPr xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><pageSetUpPr fitToPage=\"1\"/></sheetPr>'\n```\n\nDelete any `Print_Area` set on Calc sheets — conflicting scopes emit multi-page output with Assumptions / statement sheets leaking.\n\n## Build-order & cache-drift rule (critical for 3-statement)\n\nThree facts cause silent wrong numbers: (1) new formulas ship without cached values — Excel recomputes on open, HTML preview / older viewers do not; (2) downstream written in the same sequence as upstream caches `0` from upstream's pre-cache state; (3) cross-sheet `batch` while resident is open deadlocks at 3–5 ops.\n\n**Discipline (every recipe):**\n- Build order follows the data chain: `P&L → BS → CF` (3-statement); `FCF → WACC → NPV → Sensitivity` (DCF); `S&U → Debt → P&L → CF → Returns` (LBO).\n- After the cross-sheet chain, **cache-refresh pass:** re-issue `set` on every summary / valuation / balance-check cell, non-resident.\n- Spot-check: `officecli get \"$FILE\" /Summary/B2 --json | jq '.data.results[0].format.cachedValue'` returns a plausible non-null value. `null` means Excel will compute on open (OK for delivery). If a cell shows the `#OCLI_NOTEVAL!` sentinel: close residents, re-set; still unevaluated → cache-fallback (§Financial function patterns).\n\n## Recipes — three model types\n\nEach recipe below is **runnable skeleton, not finance theory**. Substitute numbers; don't restructure. All recipes assume `FILE=\"model.xlsx\"` is set and you have run `officecli create \"$FILE\"` + `officecli open \"$FILE\"`. Close with `officecli close \"$FILE\"` at the end.\n\n### Recipe A — 3-statement model (P&L + BS + CF)\n\n**What this recipe produces.** 4 sheets: `Assumptions`, `P&L`, `Balance Sheet`, `Cash Flow`, plus `Summary`. Year columns 2024A · 2025E · 2026E · 2027E. Balance-check row on BS; cash-reconciliation row on CF. Every statement row = formula → Assumptions.\n\n**Build order (MANDATORY).** `Assumptions → P&L → Balance Sheet → Cash Flow → Summary`. Do NOT build BS before P&L — `RetainedEarnings` depends on `NI`. Do NOT build CF before BS — `CF.OpeningCash = prior period CF.EndingCash` self-chain requires BS cash anchored for Y1. The skill's Gate 4 balance check fails silently if order is wrong.\n\n**Step 1 — sheets + tab colors + freeze panes.**\n\n```bash\nofficecli add \"$FILE\" / --type sheet --prop name=Assumptions --prop tabColor=FFC000\nofficecli add \"$FILE\" / --type sheet --prop name=P&L --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name='Balance Sheet' --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name='Cash Flow' --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name=Summary --prop tabColor=70AD47\nofficecli set \"$FILE\" /Assumptions --prop freeze=B2\nofficecli set \"$FILE\" /P&L --prop freeze=B3\nofficecli set \"$FILE\" \"/Balance Sheet\" --prop freeze=B3\nofficecli set \"$FILE\" \"/Cash Flow\" --prop freeze=B3\n```\n\n**Step 2 — assumptions (blue, yellow-fill on key drivers).** Year headers row 2, labels down col A, blue numeric inputs on B:E. Drivers: `RevenueGrowth`, `GrossMargin`, `OpExRatio`, `TaxRate`, `DaysReceivable/Inventory/Payable`, `CapExRatio`, `DepreciationYears`. `font.color=0000FF` on B:E. Yellow-fill (`fill=FFFF00`) the 3–5 scenario-switched drivers.\n\n**Declare named ranges for ≥3-use drivers and reference them** (`StartingARR`, `TaxRate`, `OpeningCash`, `GrowthRate`, `GrossMargin`). Formulas: `=StartingARR` not `=Assumptions!B4`; `=EBT*TaxRate` not `=EBT*Assumptions!B8`. Declared-unused names = dead decoration, Gate 6 rejects.\n\n**Step 3 — P&L rows (all formulas).** Rows: `Revenue` / `COGS` / `Gross Profit` / `OpEx` / `EBITDA` / `D&A` / `EBIT` / `Interest` / `EBT` / `Tax` / `Net Income`. Every row = formula referencing `Assumptions` or prior-row cells. Example revenue-side block — **substitute your row numbers**. Row-map for this example: `B3=Revenue, B4=COGS, B5=Gross Profit, B7=OpEx, B9=EBITDA, B10=EBIT, B15=Net Income`. Submit as single non-resident batch:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/P&L/B3\",\"props\":{\"formula\":\"Assumptions!B5\",\"font.color\":\"008000\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/C3\",\"props\":{\"formula\":\"B3*(1+Assumptions!C6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/D3\",\"props\":{\"formula\":\"C3*(1+Assumptions!D6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/E3\",\"props\":{\"formula\":\"D3*(1+Assumptions!E6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/B4\",\"props\":{\"formula\":\"-B3*(1-Assumptions!B7)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/B5\",\"props\":{\"formula\":\"B3+B4\"}}\n]\nEOF\n```\n\nAssumptions refs (`B5`, `C6`, `B7`) are also placeholder rows — better: **define named ranges** for each driver (Step 2) so formulas read `=StartingRevenue*(1+RevenueGrowth_Y2)` regardless of row layout. Repeat for `OpEx` / `D&A` / `Interest` / `Tax` / `NI`. `font.color=008000` on every cross-sheet-reference cell; same-sheet cells default `000000`. `numFmt='$#,##0;($#,##0);\"-\"'` on all $ rows.\n\n**Step 4 — Balance Sheet rows (all formulas).** Assets = `Cash + AR + Inventory + Net PP&E`. Liab = `AP + Debt`. Equity = `OpeningEquity + RetainedEarnings`. Working-capital rows use Days assumptions: `AR = Revenue × DaysReceivable / 365`. `Net PP&E` rolls forward: `Beg + CapEx − Depreciation`. **`BS.Cash` is NOT an independent plug** — it MUST equal `'Cash Flow'!B<ending-cash-row>` (populated in Step 5).\n\n**Retained Earnings — live formula every period.** `BS.RE(t) = BS.RE(t-1) + 'P&L'!NI(t) − Dividends(t)`. Hardcoded RE rounds to whole dollar → BS shows ±$1 off every period (CFO reads \"model doesn't balance\"). For Y1 Historical RE (no prior NI), compute via BS identity as a **live formula**: `BS!RE_Y1 = TotalAssets − TotalLiabilities − PaidInCapital`. Blue-font + classic comment on the Y1 cell; Y2..Y5 stay NI-driven.\n\n**Step 5 — Cash Flow rows (all formulas).** Operating: `NI + D&A − ΔWorkingCapital`. Investing: `−CapEx`. Financing: `ΔDebt − Dividends`. Ending Cash = `Opening + Operating + Investing + Financing`. **Year 2+ Opening Cash = prior period Ending Cash** — self-chain on the same sheet: `C17 = B19`, `D17 = C19`, `E17 = D19`. The Y1 `OpeningCash` is an Assumptions input.\n\n**Step 6 — Balance check + cash reconciliation rows (hard delivery checks).** Row-map for this example: `Balance Sheet: B10=Total Assets, B15=Total Liab, B17=Total Equity, B18=Balance Check`; `Cash Flow: B5=BS.Cash (cross-sheet anchor), B19=CF.Ending Cash, B21=CF-BS Cash Recon`. Substitute your layout's rows — the logic is the check, not the cell addresses.\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Balance Sheet/B18\",\"props\":{\"formula\":\"IF(ABS(B10-B15-B17)<0.01,\\\"OK\\\",\\\"IMBALANCED: \\\"&ROUND(B10-B15-B17,0))\",\"bold\":\"true\",\"font.color\":\"000000\"}},\n  {\"command\":\"set\",\"path\":\"/Cash Flow/B21\",\"props\":{\"formula\":\"IF(ABS(B19-'Balance Sheet'!B5)<0.01,\\\"OK\\\",\\\"CF != BS CASH: \\\"&ROUND(B19-'Balance Sheet'!B5,0))\",\"bold\":\"true\"}}\n]\nEOF\n```\n\nReplicate across columns C/D/E. Apply red fill (`fill=FFC7CE`) conditionally via `type=containsText --prop text=IMBALANCED` or `text=\"CF !=\"`. Gate 4 queries these rows and refuses delivery on any `IMBALANCED`.\n\n**Step 7 — cache refresh + format pass.** Re-set every summary cell on `Summary`, every balance-check / recon cell, and every cross-sheet reference on BS / CF (non-resident, single batch per sheet). Apply column widths (`col[A]=28`, `col[B:E]=15`), `numberformat='$#,##0;($#,##0);\"-\"'` on all dollar rows, header fills (`fill=1F3864`, `font.color=FFFFFF`, `bold=true`) on section-header rows (REVENUE / COGS / ASSETS / LIABILITIES). Header fill must cover A:E, not just the label cell (→ xlsx v2 §visual floor).\n\n**Step 8 — Summary / Dashboard KPIs + charts.** Minimum 4 KPIs: `Revenue 27E`, `EBITDA Margin 27E`, `Ending Cash 27E`, `Net Income CAGR` — each a formula referencing a statement cell, green font.\n\n**Minimum 3 charts on any Dashboard delivered to a board / executive audience** — one chart is a draft, three is a deliverable. Pre-populate `Summary!A10:E13` with Gross Margin / EBITDA Margin / NI Margin ratio rows (formulas referencing `P&L`) before adding the margin chart.\n\n```bash\n# (1) Top-line trend (Revenue + EBITDA).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=column --prop dataRange='P&L!A2:E5' --prop title='Revenue & EBITDA' --prop width=14cm --prop height=8cm\n# (2) Margin trend (Gross / EBITDA / NI margin).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=line --prop dataRange='Summary!A10:E13' --prop title='Margin trend' --prop width=14cm --prop height=8cm\n# (3) Cash trajectory (Ending Cash ± Runway).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=area --prop dataRange='Cash Flow!A19:E19' --prop title='Ending cash' --prop width=14cm --prop height=8cm\n```\n\n**Verification (run all three):**\n\n```bash\n# Balance check every period must say OK (range get → cells under .data.results[0].children[])\nofficecli get \"$FILE\" \"/Balance Sheet/B18:E18\" --json | jq '.data.results[0].children[] | .format.cachedValue // .text'\n# Cash recon every period must say OK\nofficecli get \"$FILE\" \"/Cash Flow/B21:E21\" --json | jq '.data.results[0].children[] | .format.cachedValue // .text'\n# Summary KPIs are plausible numbers, not null\nofficecli get \"$FILE\" \"/Summary/B2:B5\" --json | jq '.data.results[0].children[].format.cachedValue'\n```\n\n### Recipe B — DCF valuation\n\n**What this recipe produces.** Sheets: `Assumptions`, `FCF` (10-year forecast), `WACC` (panel), `DCF` (NPV + TV + equity bridge), `Sensitivity` (2-axis grid). Output: `Implied Equity Value` + `Implied Per-Share`, with a `WACC × g` sensitivity.\n\n**Build order.** `Assumptions → FCF → WACC → DCF → Sensitivity`.\n\n**Step 1 — named ranges for key drivers.** DCF's readability depends on names. Every formula below uses `WACC`, `TaxRate`, `g` — not `$B$6`:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"WACC\",\"ref\":\"WACC!$B$12\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"TaxRate\",\"ref\":\"Assumptions!$B$8\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"TerminalGrowth\",\"ref\":\"Assumptions!$B$15\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"NetDebt\",\"ref\":\"Assumptions!$B$20\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"SharesOut\",\"ref\":\"Assumptions!$B$21\"}}\n]\nEOF\n```\n\n**Step 2 — FCF build (10 years).** Columns B:K = Y1..Y10. Rows: `Revenue` (from growth) / `EBIT` (revenue × margin) / `EBIT × (1 − TaxRate)` (NOPAT) / `+ D&A` / `− CapEx` / `− ΔNWC` / `= FCF`. Use Assumptions-driven ratios (`CapEx = Revenue × CapExRatio`). All cells formulas, black font, `numFmt='$#,##0;($#,##0);\"-\"'`.\n\n**Step 3 — WACC panel.** On `WACC` sheet, an 8-row panel: `Risk-free rate` / `Equity risk premium` / `Beta` / `Cost of equity` (=Rf + β×ERP) / `Pre-tax debt cost` / `After-tax debt cost` (=×(1−TaxRate)) / `Equity weight` / `Debt weight` / `WACC` (=We×Re + Wd×Rd_after_tax). Inputs blue; derived rows black.\n\n**Step 4 — Terminal value + NPV + equity bridge.** Row-map: `DCF: B/C 3=TV, 4=PV explicit FCF, 5=PV terminal, 6=EV, 7=Net Debt, 8=Equity Value, 9=Per-Share`; `FCF: row 2 = periods (1..10), row 11 = FCF, B:K = Y1..Y10`. Substitute your rows. Notes column cells use `{\"value\":\"text\"}`, never `{\"formula\":\"...\"}` — formula-style prose yields `#NAME?` on open (see callout after Recipe C Step 5). On `DCF` sheet:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/DCF/B3\",\"props\":{\"value\":\"Terminal value (Gordon growth)\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C3\",\"props\":{\"formula\":\"FCF!K11*(1+TerminalGrowth)/(WACC-TerminalGrowth)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B4\",\"props\":{\"value\":\"PV of explicit-period FCF (10 yr)\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C4\",\"props\":{\"formula\":\"SUMPRODUCT(FCF!B11:K11/(1+WACC)^FCF!B2:K2)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B5\",\"props\":{\"value\":\"PV of terminal value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C5\",\"props\":{\"formula\":\"C3/(1+WACC)^10\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B6\",\"props\":{\"value\":\"Enterprise value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C6\",\"props\":{\"formula\":\"C4+C5\",\"bold\":\"true\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B7\",\"props\":{\"value\":\"Less: Net debt\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C7\",\"props\":{\"formula\":\"-NetDebt\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B8\",\"props\":{\"value\":\"Equity value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C8\",\"props\":{\"formula\":\"C6+C7\",\"bold\":\"true\",\"font.color\":\"000000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B9\",\"props\":{\"value\":\"Implied per-share\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C9\",\"props\":{\"formula\":\"C8/SharesOut\",\"bold\":\"true\",\"numberformat\":\"$0.00\"}}\n]\nEOF\n```\n\n**`NPV` vs `SUMPRODUCT` — both cache correctly.** The evaluator computes `NPV(rate, cross_sheet_range)` and caches the right value (verify with a readback). `SUMPRODUCT(values/(1+rate)^periods)` is the algebraic equivalent (period row `FCF!B2:K2 = 1..10` is a one-time setup); it is shown here as an OPTIONAL audit-readability choice — the explicit discounting series is sometimes easier for a reviewer to trace, not a cache workaround. Either form is fine. For irregular dates, `XNPV(rate, values, dates)` likewise caches; the SUMPRODUCT equivalent is `SUMPRODUCT(values/(1+rate)^((dates-base_date)/365))`.\n\n**Step 5 — 2-axis sensitivity grid (WACC × g).** 5×5 grid. Rows = WACC values `7.5% ... 11.5%`, cols = `g` values `1.5% ... 3.5%`. Each cell = one self-contained formula re-running the DCF with the grid's WACC and g substituted. Template:\n\n```bash\n# Cell D14 (first data cell, grid anchor at C14 = WACC label, C15 = first WACC value)\n# Substitute $D$13 (this cell's g) and $C15 (this cell's WACC) into a replicated EV + equity formula.\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Sensitivity/D15\",\"props\":{\"formula\":\"(NPV($C15,FCF!$B$11:$K$11)+(FCF!$K$11*(1+D$14)/($C15-D$14))/(1+$C15)^10+(-NetDebt))/SharesOut\",\"numberformat\":\"$0.00\"}}\n]\nEOF\n```\n\nCopy the formula across D15:H19 (5×5 grid). Row 14 carries g values (blue input); column C carries WACC values (blue input). Row 13 and column B carry labels. Apply 3-color gradient CF for quick-read (green = upside, red = downside):\n\n```bash\nofficecli add \"$FILE\" /Sensitivity --type conditionalformatting \\\n  --prop type=colorScale --prop ref=D15:H19\n```\n\n**No Excel Data Tables.** Excel's native `/Data/Table` 2-variable table is not reliably supported via the CLI — each grid cell MUST be an explicit formula. Copy the template, do not try `Data Table` input cells.\n\n**Verification.**\n\n```bash\nofficecli get \"$FILE\" \"/DCF/C8\" --json | jq '.data.results[0].format.cachedValue'   # equity value, plausible $\nofficecli get \"$FILE\" \"/DCF/C9\" --json | jq '.data.results[0].format.cachedValue'   # per-share, in $XX.XX range\nofficecli get \"$FILE\" \"/Sensitivity/F17\" --json | jq '.data.results[0].format.cachedValue'   # grid center cell, plausible\n```\n\nIf `C8` or `C9` come back `null` or show the `#OCLI_NOTEVAL!` sentinel, re-set them (non-resident) — see §Build-order & cache-drift.\n\n### Recipe C — LBO model\n\n**What this recipe produces.** Sheets: `Assumptions`, `S&U` (Sources & Uses), `Debt` (multi-tranche schedule), `P&L` (5-yr), `CF`, `Exit` / `Returns`. Outputs: `MOIC`, `IRR`, and a 4-tier returns waterfall. LBO is the stress test — expect circular refs (interest ↔ cash), deepest cross-sheet chains, and the heaviest use of named ranges.\n\n**Build order.** `Assumptions → S&U → P&L → Debt → CF → Exit → Returns`. P&L before Debt (debt interest depends on P&L EBIT for coverage checks); Debt before CF (CF uses interest + principal amortization). Enable `calc.iterate` before Step 5.\n\n**Step 1 — Sources & Uses (balance required, every fee line itemized).**\n\n```\nUses    = Purchase_EV (EntryEBITDA × EntryMultiple) + Transaction_fees (Purchase_EV × TxnFeePct, typ 1.5–2.5%)\n        + Financing_fees ((Senior + Mezz) × FinFeePct, typ 1–3%) + Refinanced_debt\nSources = Senior_TLB + Mezz + Revolver_drawn + Sponsor_equity\n```\n\n**Sponsor equity — pick one, never both.** (a) **Stated:** `Sponsor_equity = Assumptions!SponsorEquity`, then scale senior/mezz so Sources = Uses (fees absorbed by debt, not a silent plug). (b) **Solved:** `Sponsor_equity = Uses − Senior − Mezz − Revolver − Refinanced`, label \"Sponsor Equity (solved)\", no standalone Assumptions ref. Hardcoded `SponsorEquity` PLUS a `=Uses − Senior − Mezz` plug guarantees silent fee absorption — stated $140M vs plug $194.67M = $54.67M unaccounted fees, CFO rejection on sight.\n\n```bash\n# Sources = Uses hard check.\nofficecli set \"$FILE\" /S&U/B12 --prop formula='IF(ABS(SUM(B4:B7)-SUM(B9:B11))<1,\"BALANCED\",\"S&U IMBALANCE: \"&ROUND(SUM(B4:B7)-SUM(B9:B11),0))' --prop bold=true\n\n# Stated-vs-plug consistency (Gate 4 addendum; only run if you chose pattern (a)).\nSTATED=$(officecli get \"$FILE\" /Assumptions/B12 --json | jq -r '.data.results[0].format.cachedValue // \"null\"')\nPLUGGED=$(officecli get \"$FILE\" /S&U/B10 --json | jq -r '.data.results[0].format.cachedValue // \"null\"')   # B10 = sponsor-equity row on S&U\nDELTA=$(python3 -c \"print(abs(float('$STATED') - float('$PLUGGED')))\" 2>/dev/null || echo 99999)\npython3 -c \"import sys; sys.exit(0 if float('$DELTA') <= 1 else 1)\" && echo \"S&U sponsor OK (stated=$STATED plug=$PLUGGED)\" || { echo \"REJECT Gate 4 S&U: stated $STATED ≠ plug $PLUGGED (Δ=$DELTA) — fees silently absorbed\"; exit 1; }\n```\n\nEvery non-sponsor line on `S&U` is a blue Assumptions input (target EBITDA, entry multiple, fee %s) or a derived formula. No hardcoded Uses / Sources numbers.\n\n**Step 2 — Debt schedule (multi-tranche).** One row per tranche per year. Columns: `BeginningBalance` / `Mandatory amortization` / `Cash sweep` / `EndingBalance` / `AverageBalance` / `InterestExpense`. Senior TLB: 1% mandatory amortization + all excess cash to sweep. Mezz: 0% amortization, interest-only cash-pay. Row-map for this example (senior TLB tranche, year 2 column C): `C4=Beginning Balance, C5=Mandatory Amort, C6=Ending Balance, C7=Cash Sweep, C8=Average Balance, C9=Interest Expense`. `CF!C20` = free cash available to sweep (year-2 ending cash pre-sweep on CF sheet). Substitute your tranche row block per layout.\n\n```bash\n# year 2 senior TLB\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Debt/C4\",\"props\":{\"formula\":\"B6\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C5\",\"props\":{\"formula\":\"-C4*Assumptions!$B$30\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C6\",\"props\":{\"formula\":\"C4+C5+C7\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C7\",\"props\":{\"formula\":\"-MIN(-CF!C20,C4+C5)\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C8\",\"props\":{\"formula\":\"(C4+C6)/2\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C9\",\"props\":{\"formula\":\"-C8*Assumptions!$B$31\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}}\n]\nEOF\n# Add the sweep-rule comment as a classic comment (comment is NOT a cell prop — separate --type comment).\nofficecli add \"$FILE\" /Debt --type comment --prop ref=C7 --prop text='cash sweep capped at available cash and remaining tranche balance'\n```\n\n**Revolver capacity cap.** If your deal uses a revolver tranche, the revolver balance each period is bounded by the commitment ceiling:\n```\nRevolver_Balance = MIN(Assumptions!RevolverCapacity, MAX(0, prior_revolver + draw − paydown))\n```\nWithout the `MIN(capacity, ...)` outer, a shortfall quarter silently over-draws the facility.\n\nAdjust row indices to your layout. Repeat for each tranche (senior / mezz / revolver) and each year.\n\n**Step 3 — P&L (5-year) + interest from Debt.** P&L interest row pulls from Debt: `Interest = 'Debt'!TotalInterestRowY<N>`. This creates the **circular reference**: Interest → NI → CF → Cash Sweep → Debt balance → Interest.\n\n**Write-order warning.** `calc.iterate=true` governs _recalculation_, not write-phase. Appending the closing leg of a cross-sheet ring to a file that already contains the ring deadlocks the engine at 100% CPU regardless of `iterate`. For complex rings (multi-tranche LBO, revolver + TLB + mezz), use §Write-order surgery below (de-ring → write downstream → re-ring). Enable `calc.iterate=true` BEFORE writing ring formulas:\n\n```bash\nofficecli set \"$FILE\" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001\n```\n\n`iterate` converges via successive approximation for naturally-dampening loops (higher interest → less cash → less sweep → higher balance, bounded by EBIT). `#REF!` or divergent values = pause; fix algebra, do not raise `iterateCount` to 1000.\n\n**Step 4 — CF + cash sweep.** Ending cash = Opening + CFO − CapEx − Mandatory amort − Cash sweep. Cash sweep = `MIN(freeCashAfterCapEx, seniorDebtBalance + seniorMandatoryAmort)`. The `MIN` cap prevents swept-below-zero.\n\n**Step 5 — Exit + Returns.** Row-map: `Exit: B3=Exit EV, B4=Less: remaining debt, B5=Exit equity to sponsor`; `Returns: B3=MOIC, B4=IRR`.\n\n```bash\n# Values/formulas — single non-resident batch.\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Exit/B3\",\"props\":{\"formula\":\"'P&L'!F8*Assumptions!$B$25\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Exit/B4\",\"props\":{\"formula\":\"-('Debt'!F6+'Debt'!F13)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Exit/B5\",\"props\":{\"formula\":\"B3+B4\",\"bold\":\"true\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Returns/B3\",\"props\":{\"formula\":\"'Exit'!B5/('S&U'!B9)\",\"numberformat\":\"0.00\\\"x\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Returns/B4\",\"props\":{\"formula\":\"IRR({-'S&U'!B9,0,0,0,0,'Exit'!B5})\",\"numberformat\":\"0.0%\"}}\n]\nEOF\n# Classic comments — one --type comment per anchor cell.\nofficecli add \"$FILE\" /Exit --type comment --prop ref=B3 --prop text='Exit EV = Y5 EBITDA × exit multiple'\nofficecli add \"$FILE\" /Returns --type comment --prop ref=B3 --prop text='MOIC = exit equity / sponsor equity'\nofficecli add \"$FILE\" /Returns --type comment --prop ref=B4 --prop text='IRR — 5-yr, entry + exit only; use XIRR for mid-year dividends'\n```\n\n**Callout — labels: `comment` element vs Notes column vs `formula` (three distinct mechanics).**\n- **Hover tooltip** → `officecli add ... --type comment --prop ref=<cell> --prop text='...'`. The **`comment` key is NOT a valid prop on `set cell`** (not in `officecli help xlsx cell`) — it silently drops when embedded inside a `set cell` props dict. Use the dedicated element.\n- **Visible text in an adjacent Notes column** → `{\"command\":\"set\",\"path\":\"/DCF/D3\",\"props\":{\"value\":\"TV = FCF × (1+g) / (WACC−g)\"}}` — **`value`, not `formula`**, plain quoted string.\n- **Formula-style prose written as a real formula** → NEVER. `{\"formula\":\"FCF10*(1+g)/(WACC-g)\"}` produces `#NAME?` in Excel (`FCF10`, `g`, `WACC` are unbound identifiers in that cell context).\n\nFor mid-year dividends or partial exits, use `XIRR({cashflows}, {dates})` instead of `IRR`.\n\n**Step 6 — Returns waterfall (optional, 4-tier LP/GP).** Tiers: (1) LP preferred return 8% ; (2) GP catch-up to 20% ; (3) 80/20 split above hurdle ; (4) 100% to LP on loss. Each tier is a `MAX(0, MIN(...))` clamp. See §Sensitivity & scenarios for the general grid pattern.\n\n**Verification.**\n\n```bash\nofficecli get \"$FILE\" /S&U/B12 --json | jq '.data.results[0].format.cachedValue // .data.results[0].text'   # must say BALANCED\nofficecli get \"$FILE\" /Returns/B3 --json | jq '.data.results[0].format.cachedValue'                # MOIC, expect 2.0x-4.0x typical\nofficecli get \"$FILE\" /Returns/B4 --json | jq '.data.results[0].format.cachedValue'                # IRR, expect 0.15-0.30 typical\n# Iterate converged?\nofficecli query \"$FILE\" 'cell:contains(\"#REF!\")' --json | jq '.data.results | length'   # must be 0\n```\n\n## Sensitivity & scenarios\n\n**Three patterns, pick one:**\n- **(a) Base / Upside / Downside columns** on Assumptions — side-by-side scenarios, dropdown-less switch via an \"Active\" column + `INDEX/MATCH`.\n- **(b) Dropdown + `INDEX/MATCH` switch** — one validation dropdown on Summary drives every driver via `INDEX(Base:Downside, MATCH(Dropdown, ScenLabels, 0))`.\n- **(c) 2-axis sensitivity grid** — 5×5 or 7×7, one self-contained formula per cell, row/col headers are the two drivers. See Recipe B Step 5 for WACC × g.\n\nMixing (a)+(b) creates circular input (scenario picked by dropdown AND overwritten by Active column) — pick one.\n\n**Grid rule:** each cell substitutes row-driver and col-driver into a self-contained copy of the output formula. Cannot reference the `WACC` named range (that's the panel) — reference the grid's axis cell.\n\n**Dropdown scenario switch.** One `validation` dropdown on Summary drives every `Assumptions` row:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/Summary\",\"type\":\"validation\",\"props\":{\"sqref\":\"B1\",\"type\":\"list\",\"formula1\":\"Base,Upside,Downside\"}},\n  {\"command\":\"set\",\"path\":\"/Assumptions/B5\",\"props\":{\"formula\":\"INDEX(C5:E5,MATCH(Summary!$B$1,$C$4:$E$4,0))\"}}\n]\nEOF\n# If you want a hover tooltip on B5, add it separately:\nofficecli add \"$FILE\" /Assumptions --type comment --prop ref=B5 --prop text='Revenue growth — picked by Summary!B1 scenario dropdown'\n```\n\nEvery `Assumptions` driver row gets the same `INDEX/MATCH`. Base / Upside / Downside columns on C:E stay blue (hardcoded scenario inputs).\n\n**Football-field chart pattern (DCF valuation summary).** Horizontal Low→High bars for 3–5 valuation methods (DCF base, DCF bear, Trading comps, Precedent txns, LBO floor) stacked vertically. On a `Football` sheet: col A = method label, col B = Low $, col C = High $, col D = `=C−B` (width). Chart as a stacked bar with column B as an invisible first series (white/no-fill) and column D as the visible series — `dataRange=Football!A3:D7`, `chartType=bar`. Excel reads this as a floating bar per method.\n\n## Financial function patterns\n\nTerse reference — not a finance textbook. If you don't know what these do, pause and ask the user.\n\n| Function | Prefer over | Why |\n|---|---|---|\n| `XNPV(rate, values, dates)` | `NPV` | Irregular cash flow dates (M&A close mid-year, staggered tranches) |\n| `XIRR(values, dates)` | `IRR` | Irregular dates; multiple sign changes handled better |\n| `INDEX(range, MATCH(lookup, key, 0))` | `VLOOKUP` | Insert-safe (VLOOKUP breaks when a column is inserted in the source range) |\n| `IFERROR(x/y, 0)` or `IF(y=0, 0, x/y)` | bare division | Guard every `/` in a financial model — `#DIV/0!` shipped = delivery failure |\n| `MIRR(values, financeRate, reinvestRate)` | `IRR` with sign flips | When cash-flow pattern has 2+ sign changes |\n| `SUMIFS(sumRange, criteriaRange1, criterion1, ...)` | `SUMPRODUCT((...))` array | Clearer intent for conditional sums; either evaluates correctly |\n\n**Evaluator coverage — verify, don't preemptively hardcode.** The CLI evaluator computes the finance functions this skill uses — `NPV` / `XNPV` / `IRR` / `XIRR`, array-literal formulas (`IRR({...})`), and `SUMPRODUCT(1/COUNTIF(range,range))` distinct-count — and caches the correct value with `evaluated:true`. There is no `NPV→0` rewrite obligation and no distinct-count `1/N` trap. The honest rule: build the formula you mean, then verify the cached value with a readback (`get ... --json | jq '.data.results[0].format.cachedValue'`). Only if a specific cell genuinely comes back with the `#OCLI_NOTEVAL!` sentinel (formula written before its inputs, or an unsupported function) should you fall back — first re-set non-resident after `close`; if it still won't evaluate, hardcode the computed value with a blue font and a classic comment via `officecli add \"$FILE\" /Sheet --type comment --prop ref=<cell> --prop text='cached valuation; refreshes on open in Excel — do not edit'`, and disclose in delivery notes.\n\n## Circular references & iterative calc\n\n**Enable `calc.iterate` ONLY when circularity is algebraically justified:** Interest ↔ Cash (LBO revolver / cash sweep), Tax shield ↔ NI (rare — most 3-statement models compute interest before tax and avoid), Revolver plug ↔ Ending cash (corporate cash waterfall with min-cash).\n\n```bash\nofficecli set \"$FILE\" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001\n```\n\n`iterateCount=100` / `iterateDelta=0.001` are Excel defaults, fine for naturally dampening loops.\n\n### Write-order surgery (de-ring → write downstream → re-ring)\n\n`calc.iterate` controls recalc, not write-phase. Appending the closing leg of an already-wired cross-sheet ring (Debt.Interest ↔ CF.Cash ↔ Debt.CashSweep) deadlocks at 100% CPU; `view html` / `get` also hang on a non-converged ring.\n\n**3-step playbook:**\n1. **De-ring** — write Debt with the 10–20 ring cells set to literal `0` (e.g. `C7=0`, not `=-MIN(...)`). Removes the ring.\n2. **Write downstream** — build all non-circular chains (P&L, CF, Exit, Returns, Summary, grid) non-resident, one heredoc per sheet. Everything caches against the zeroed cells.\n3. **Re-ring** — close all residents, re-set each circular cell with its real formula, one `set` per cell, non-resident.\n\n**Acceptance.** `get /Debt/C7 --json | jq '.data.results[0].format.cachedValue'` returns non-zero non-null. If a cell still deadlocks, leave `=0` + classic comment `\"circular; recalculates in Excel on F9\"`, flag at delivery. Never paper over with `iterateCount=1000`.\n\n**Do NOT use `iterate` as a band-aid for `#REF!` / divergent values.** Raising `iterateCount` to 1000 hides the bug and ships a plausibly-wrong value; `validate` does not catch it. Break the loop algebraically (e.g. interest on opening balance only, not average).\n\n**Verify convergence.** Read the loop cell, bump a driving assumption and back, re-read — values must match:\n\n```bash\nV1=$(officecli get \"$FILE\" /Debt/C9 --json | jq '.data.results[0].format.cachedValue')\nofficecli set \"$FILE\" /Assumptions/B31 --prop value=0.085\nofficecli set \"$FILE\" /Assumptions/B31 --prop value=0.0845\nV2=$(officecli get \"$FILE\" /Debt/C9 --json | jq '.data.results[0].format.cachedValue')\n[ \"$V1\" = \"$V2\" ] && echo \"Iterate converged\" || echo \"WARN: drift V1=$V1 V2=$V2 — tighten iterateDelta or check algebra\"\n```\n\n## Audit & Delivery Gate\n\n**Assume there are problems.** First build is almost never correct. Run every gate below; every check must print its success line. `validate` passing is not delivery — the model can pass schema and still be wrong by a factor of 10.\n\n### Gates 1–3 — inherited from xlsx v2 verbatim\n\n→ see xlsx v2 §QA minimum cycle (Gates 1–3 cover `view issues`, error-cell query, `validate` after close). Run them first, exactly as written in xlsx v2. No financial-model-specific tweaks.\n\n### Gate 4 — statement integrity (3-statement & LBO)\n\nBalance-check and cash-reconciliation rows produced by Recipe A / C must show `OK` / `BALANCED` every period. `query` the check rows and refuse on any `IMBALANCED` / `CF !=`:\n\n```bash\nBS_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"IMBALANCED\")' --json | jq '.data.results | length')\nCF_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"CF !=\")' --json | jq '.data.results | length')\nSU_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"S&U IMBALANCE\")' --json | jq '.data.results | length')\nif [ \"$BS_FAIL\" -eq 0 ] && [ \"$CF_FAIL\" -eq 0 ] && [ \"$SU_FAIL\" -eq 0 ]; then\n  echo \"Gate 4 OK (balance + recon + S&U all pass)\"\nelse\n  echo \"REJECT Gate 4: BS=$BS_FAIL CF=$CF_FAIL S&U=$SU_FAIL\"; exit 1\nfi\n```\n\nIf any fail, the model is silently wrong — fix the upstream chain before delivery. Most common cause: a cross-sheet formula stored `\\!` (shell-mangled) — run `officecli query \"$FILE\" 'cell:contains(\"\\\\\\\\!\")'` and re-enter via batch heredoc.\n\n### Gate 5 — cached-value sanity on valuation cells\n\nNPV / IRR / XIRR / equity-bridge / MOIC / summary KPI cells that ship unevaluated (`#OCLI_NOTEVAL!` sentinel, typically because the formula was written before its inputs) send a blank/wrong number to a reader who does not recalc on open. List every valuation cell and confirm a cached value is present:\n\n```bash\n# Customize the path list per recipe — this is the DCF example\nfor P in \"/DCF/C4\" \"/DCF/C5\" \"/DCF/C6\" \"/DCF/C8\" \"/DCF/C9\"; do\n  V=$(officecli get \"$FILE\" \"$P\" --json | jq -r '.data.results[0].format.cachedValue // \"null\"')\n  if [ \"$V\" = \"null\" ] || [ \"${V#\\#OCLI_NOTEVAL}\" != \"$V\" ]; then\n    echo \"REJECT Gate 5: $P unevaluated (cached=$V) — re-set after close (see §Build-order & cache-drift)\"; exit 1\n  fi\n  echo \"Gate 5 $P: cached=$V OK\"\ndone\n```\n\nFor LBO, extend the list: `/Exit/B5`, `/Returns/B3`, `/Returns/B4`. For 3-statement, extend with `/Summary/B2:B5`.\n\n### Gate 6 — hardcode / zone discipline\n\nEvery Calc sheet has zero numeric hardcodes. Executable:\n\n```bash\n# `cell:not(:has(formula))` selects the literal cells (and `cell:has(formula)` the formula cells).\nHARDCODE=$(officecli query \"$FILE\" 'cell[type=Number]' --json \\\n  | jq '[.data.results[] | select(.format.formula == null) | select(.path | test(\"/(P&L|Balance Sheet|Cash Flow|DCF|Debt|FCF|WACC|Exit|Returns)/\"))] | length')\n[ \"$HARDCODE\" -eq 0 ] && echo \"Gate 6 OK (no hardcodes on Calc sheets)\" || { echo \"REJECT Gate 6: $HARDCODE hardcoded numeric cells on Calc zone — move to Assumptions\"; exit 1; }\n\n# Named-range coverage + dead-decoration audit: ≥3 ranges declared AND each referenced by ≥1 formula.\nNR=$(officecli query \"$FILE\" namedrange --json | jq '.data.results | length')\n[ \"$NR\" -ge 3 ] && echo \"Gate 6 OK ($NR named ranges)\" || echo \"WARN Gate 6: only $NR named ranges\"\nDEAD=0\nfor NR_NAME in $(officecli query \"$FILE\" namedrange --json | jq -r '.data.results[].format.name'); do\n  # Match the formula SOURCE (`formula~=`), not the cached/displayed RESULT — `:contains` would scan\n  # the computed value and report every name as dead (false reject).\n  USES=$(officecli query \"$FILE\" \"cell[formula~=$NR_NAME]\" --json | jq '.data.results | length')\n  [ \"$USES\" -ge 1 ] && echo \"  $NR_NAME: $USES uses OK\" || { echo \"  WARN: $NR_NAME unused\"; DEAD=$((DEAD+1)); }\ndone\n[ \"$DEAD\" -eq 0 ] && echo \"Gate 6 named-range audit OK\" || { echo \"REJECT Gate 6: $DEAD dead-decoration name(s)\"; exit 1; }\n```\n\n### Gate 5b — visual audit via HTML preview (mandatory)\n\nGates 1–4/6 are grep defenses — they cannot see a rendered sheet. Run `officecli view \"$FILE\" html` and Read the returned HTML. Walk every sheet (inherits xlsx v2 visual floor):\n\n- No `###` in any numeric cell (widen column).\n- No truncated labels / section headers (widen column or `alignment.wrapText=true`).\n- No placeholder tokens (`TBD`, `{var}`, `xxxx`) — Gate 6.1 grep below.\n- Balance-check / recon rows say `OK` / `BALANCED` every period column.\n- Dashboard charts render, y-axis = 0 on ARR/revenue lines, source data matches statement sheet.\n- Sensitivity grid colors read green (upside) → red (downside) — color-scale CF applied.\n- No stale cached `0` on summary KPIs; if present, run cache-refresh pass.\n\nREJECT on any defect. **Human preview:** `officecli watch \"$FILE\"`, or open in Excel / WPS / Numbers — final colors + chart fidelity only fully render in the target viewer.\n\n### Gate 6.1 — token / placeholder sweep\n\n```bash\nLEAK=$(officecli view \"$FILE\" text | grep -niE 'TBD|\\(fill in\\)|xxxx|lorem|\\{\\{|placeholder|coming soon')\n[ -z \"$LEAK\" ] && echo \"Gate 6.1 OK (no placeholder tokens)\" || { echo \"REJECT Gate 6.1:\"; echo \"$LEAK\"; exit 1; }\n```\n\n### Honest limit\n\n`validate` catches schema errors, not finance errors. A model passes `validate` with `BS.Cash` hardcoded to force balance, an `NPV` cached at `0`, a sensitivity grid all-zero because it was built before FCF, a `#NAME?` runtime on a `P&L`-named sheet with unquoted refs. Gates 4 / 5 / 6 / 5b exist because schema-level `validate` cannot catch any of this.\n\n## Known Issues & Pitfalls\n\n→ Base pitfalls (cross-sheet `!` trap, batch JSON dotted-name rule, renderer caveats): see xlsx v2 §Known Issues & Pitfalls — all apply.\n\nFinancial-model-specific:\n\n- **AP sign on COGS.** Accounts Payable: if COGS is stored negative on the P&L, AP formula must negate — `=-COGS*DaysPayable/365`. Wrong sign inflates NWC and flips CF direction. Silent; passes `validate`.\n- **`#NAME?` not caught by `query` / `validate`.** A cross-sheet formula referencing `P&L!B3` without quoting the sheet name (because `&` is special) lands at runtime as `#NAME?`. Always write cross-sheet refs as `'P&L'!B3` — single-quote the sheet name if it contains `&`, space, `(`, `)`, etc. Gate 5b visual check is the only detection.\n- **Iterative calc silent non-convergence.** `calc.iterate=true iterateCount=100` converges at whatever the cap lands on — even if the true answer is 2× that. Always run convergence verify (§Circular references). Complex LBO rings (multi-tranche debt + sweep + tax shield) may not converge; when `cachedValue=0` on a ring cell, use §Write-order surgery.\n- **Batch-while-resident deadlock on circular writes.** Writing the closing leg of a cross-sheet ring via `batch` with a resident open deadlocks at 100% CPU. Even single `set` on a ring cell can hang. Fix: close residents, write the ring in two passes per §Write-order surgery. Non-resident single-heredoc is the only safe form.\n- **Cross-sheet cached value stale in `view html`.** Downstream written in the same sequence as upstream caches `0`. Excel resolves on open; HTML preview does NOT. Re-set every downstream non-resident after the chain (§Build-order & cache-drift).\n- **`NPV()` / `XNPV()` evaluate and cache correctly.** The evaluator computes both (same-sheet and cross-sheet); no rewrite is required. `SUMPRODUCT(values/(1+rate)^periods)` remains an optional audit-readability alternative, not a cache workaround.\n- **Sensitivity-grid build order still matters.** A grid cell written before its FCF/WACC inputs exist evaluates against empty inputs and may cache a wrong/blank value. Build FCF + WACC + DCF first, then the grid in a separate non-resident batch, and verify (`jq '.data.results[0].format.cachedValue'`). This is an ordering discipline, not an evaluator limitation.\n- **`BS.Cash` = CF ending cash always** (including Y1: `BS.Cash = 'Cash Flow'!B19`). Never an independent plug or Assumptions ref — a plugged `BS.Cash` hides balance errors.\n- **Year 2+ `Opening Cash` = prior period `Ending Cash`** (`C17=B19`, `D17=C19`). Independent Y2+ opening-cash inputs silently drift from BS.\n- **Waterfall chart \"total\" bars.** `chartType=waterfall` cannot mark total programmatically — use `colors=` convention (dark = total, medium = positive, red = negative). See `help xlsx chart`.\n- **DCF per-share when `SharesOut` is a formula.** `=BasicShares + OptionPool × ExerciseAssumption` → add a blue-font assumption cell and point the `SharesOut` named range at the computed cell, not the raw input.\n\n## Help pointer\n\nWhen in doubt: `officecli help xlsx [element] [--json]`. Help is the authoritative schema; this skill is the decision guide for financial-modeling deltas.\n","skills/officecli-pptx/SKILL.md":"---\nname: officecli-pptx\ndescription: \"Use this skill any time a .pptx file is involved -- as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file; editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions 'deck', 'slides', 'presentation', 'pitch', or references a .pptx filename.\"\n---\n\n# OfficeCLI PPTX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good slides look like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help pptx                         # List all pptx elements\nofficecli help pptx <element>               # Full element schema (e.g. shape, chart, animation, connector, zoom, group)\nofficecli help pptx <verb> <element>        # Verb-scoped (e.g. add shape, set slide)\nofficecli help pptx <element> --json        # Machine-readable schema\n```\n\nHelp reflects the installed CLI version. When skill and help disagree, **help is authoritative**. Triggers to run help immediately: `UNSUPPORTED props:` warning, unknown animation preset, `connector.shape=` enum drifts, prop-vs-alias (`lineWidth` vs `line.width`, `color` vs `font.color`).\n\n## Shell & Execution Discipline\n\n**Shell quoting (zsh / bash).** ALWAYS quote element paths (`\"/slide[1]/...\"`) — zsh globs unquoted `[1]` to `no matches found`. Escaping happens at three layers — keep them separate (the CLI handles the second for you):\n\n1. **Shell.** `$` in a value still belongs to the shell — single-quote the whole value: `--prop text='$15M'`. Double-quoted `\"$15M\"` gets expanded to `M`. The CLI does NOT unescape `\\$` for you.\n2. **CLI (`text=`).** The two-char escapes `\\n` and `\\t` ARE interpreted, consistently across pptx / docx / xlsx — `\\n` is a line / paragraph break, `\\t` is a tab. To produce a literal backslash-n in text, double it (`\\\\n`); this is rarely what you want.\n3. **JSON (batch heredoc).** The recipes below pipe `cat <<EOF | officecli batch` **unquoted** so `$SLIDE` / `$FILE` expand inside the body. That same unquoted heredoc also expands a literal `$` in a value: `\"$1.42\"` silently becomes `.42` and `\"$2.4B\"` becomes `.4B` (`$1` / `$2` are empty shell params). **Escape currency as `\\$`** — `\"text\":\"\\$1.42\"` — which still lets `$SLIDE` expand. `\\n` / `\\t` inside the JSON work either way. A fully-quoted `<<'EOF'` protects every `$` but then `$SLIDE` won't expand, so only use it when the body has no shell variables. After writing money values, `view text` and confirm the `$` survived.\n\nIf in doubt, `view text` after writing and compare character-for-character.\n\n**Incremental execution.** One command → check exit code → continue. A 50-command script that fails at command 3 cascades silently. After any structural op (new slide, chart, animation, connector) run `get` before stacking more.\n\n## Requirements for Outputs\n\nThese are the deliverable standards every deck MUST meet. Violating any one = not done, regardless of content quality.\n\n### All decks\n\n**One idea per slide.** If a slide needs a second title to explain what it covers, split it. Dense \"everything about X\" slides lose the audience inside 3 seconds. Use a section divider to group related one-idea slides, not a mega-slide.\n\n**Explicit type hierarchy — do NOT rely on theme defaults.** Theme defaults drift between masters. Set sizes explicitly on every text shape.\n\n| Element | Minimum | Typical | Min shape height |\n|---|---|---|---|\n| Slide title | **≥ 36pt** bold | 36–44pt | ≥ 2cm |\n| Section / subtitle | ≥ 20pt | 20–24pt | ≥ 1.2cm |\n| Body text | **≥ 18pt** | 18–22pt | ≥ 1cm |\n| Caption / axis label | ≥ 10pt muted | 10–12pt | ≥ 0.6cm |\n\nRule of thumb: **min shape height ≈ font_pt × 0.05cm**. An 18pt sublabel in a 0.8cm-tall box will overflow — `view annotated` catches this.\n\nTitle must be **≥ 2× body size** (36pt over 20pt works; 28pt over 20pt looks timid). Four legit exceptions to body ≥ 18pt: chart axis labels, legends, footer / page number, and ≤ 5-word KPI sublabels (e.g. \"Active users\"). Descriptive sentences must be ≥ 18pt. Left-align body; center only titles and hero numbers. If \"the cards won't fit\", drop cards instead of shrinking font.\n\n**Two fonts max, one palette.** One heading font + one body font (e.g. Georgia + Calibri) — a third *display* face is fine only for big numerals or the cover title, as long as that heading+body pair stays intact. One dominant brand color (60–70% weight) + one supporting + one accent. Never mix 4+ colors in body content. **The palettes and font pairings in Design Principles are a floor, not a menu:** if the user gave brand colors/fonts or an existing template, match those first; otherwise the named sets are calibrated seeds — blend or diverge freely, as long as the result isn't *worse* than them and still clears the contrast floor.\n\n**Every slide carries a non-text visual — one that informs.** Shape, chart, icon, gradient band that carries meaning, not decoration. A bullet-only deck is interchangeable with a Word doc. Exceptions: literal quote slides, code blocks, a single summary-table slide.\n\n**Less is more — every element earns its place.** The visual rule above guards against bullet-walls; it is not licence to clutter. Don't pad with decorative stats, icons, or filler sections that don't inform (\"data slop\"). If a slide feels empty, fix it with layout and whitespace, not invented content — cut scope rather than bulk it up, and flag a larger addition instead of making it unprompted.\n\n**Speaker notes on every content slide.** `--type notes --prop text=\"...\"`. The speaker needs a script; the audience shouldn't read the slide verbatim.\n\n**Copy reads human, not AI.** Titles orient on content, not punchline. No \"It's not X. It's Y.\", no manufactured tension, no faux-insight (\"The magic moment\"), no one-word drama (\"Momentum.\"). Cut hype adjectives (seamless, robust, game-changing) — let the number carry it.\n\n**Preserve existing templates.** When a file already has a theme and masters, match them. Existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY deck)\n\nBefore declaring done, the per-slide render (see QA) MUST satisfy:\n\n- **No placeholder tokens rendered as content.** `{{name}}`, `$fy$24`, `<TODO>`, `lorem`, `xxxx`, empty `()`/`[]` in chart titles never appear.\n- **No overflow off-edge, no clipped text in shapes.** `view issues` flags both (`shape_off_slide` + a text-fit hint). To fix a clip: grow the box or shorten the value — never trim content to fit.\n- **Cover carries its orienting elements.** Title + subtitle + presenter/client + date + a brand band or key-takeaway strap — a title-only cover reads as a stub. Generous whitespace around them is still right; rich ≠ crowded.\n- **Contrast.** `view issues` auto-flags the common case — opaque dark text on a shape's own dark fill (`low_contrast`). It can't see the rest: icon / chart-series fills, scheme/inherited colors, or text over a *separate* background shape. So on any fill with brightness < 30% (`1E2761`, `36454F`, deep forest / berry / cherry), still confirm every body run, card body, chart series, and icon is `FFFFFF` or brightness > 80% — mid-gray (`6B7B8D` ≈ 44%) reads on a laptop and vanishes on projection. Spot-check via `view html` after the dark-fill pass.\n\nIf any fails, STOP and fix before declaring done.\n\n## Design Principles\n\nA deck is not a document. The audience has 3 seconds to get each slide. Before adding anything, ask: \"If the audience reads only the biggest element and glances once, do they get the point?\" If they have to read the bullets, the biggest element is wrong.\n\n### Grid, margins, negative space\n\nStandard widescreen is **33.87 × 19.05cm**. Treat it as a 12-column grid internally:\n\n- **Edge margin ≥ 1.27cm** (0.5\") on all sides.\n- **Inter-block gap ≥ 0.76cm** (0.3\") between cards / columns / rows — pick one value (0.76 or 1.27cm) and use it everywhere; mixed gaps look unfinished.\n- **≥ 20% negative space per slide.** Filling every pixel reads as amateur.\n- **Compose, don't web-center.** Whitespace is structural: a slide top-weighted with open space in the lower third is correct composition, not an empty defect. Intentional asymmetry (content left, breathing room right) reads more designed than centering everything — don't fill a gap just because it's there.\n- For card grids: `usable = 33.87 − 2·margin − (N−1)·gap`, then `col_width = usable / N`. Don't hand-pick x coordinates.\n\n### Font pairings\n\nPair by document register, not by novelty. \"Best For\" is a prompt, not a decree; a pairing outside this table is fine if it fits — these 8 are seeds, not the set.\n\n| Header | Body | Best For |\n|---|---|---|\n| Georgia | Calibri | Formal business, finance, executive reports |\n| Arial Black | Arial | Bold marketing, product launches |\n| Calibri | Calibri Light | Clean corporate, minimal design |\n| Cambria | Calibri | Traditional professional, legal, academic |\n| Trebuchet MS | Calibri | Friendly tech, startups, SaaS |\n| Impact | Arial | Bold headlines, event decks, keynotes |\n| Palatino | Garamond | Elegant editorial, luxury, nonprofit |\n| Consolas | Calibri | Developer tools, technical / engineering |\n\nSet both fonts explicitly on every shape (`--prop font=Georgia` on titles, `--prop font=Calibri` on body), not via theme inheritance.\n\n### Color and contrast\n\nThe columns: **Primary** (dominant — 60–70% of weight, the color you see first), **Secondary** (supporting tone), **Accent** (sparing, one-hit emphasis), **Text** (body on light fills), **Muted** (captions / axis labels / footer).\n\n| Theme | Primary | Secondary | Accent | Text | Muted |\n|---|---|---|---|---|---|\n| Coral Energy | `F96167` | `F9E795` | `2F3C7E` | `333333` | `8B7E6A` |\n| Midnight Executive | `1E2761` | `CADCFC` | `FFFFFF` | `333333` | `8899BB` |\n| Forest & Moss | `2C5F2D` | `97BC62` | `F5F5F5` | `2D2D2D` | `6B8E6B` |\n| Charcoal Minimal | `36454F` | `F2F2F2` | `212121` | `333333` | `7A8A94` |\n| Warm Terracotta | `B85042` | `E7E8D1` | `A7BEAE` | `3D2B2B` | `8C7B75` |\n| Berry & Cream | `6D2E46` | `A26769` | `ECE2D0` | `3D2233` | `8C6B7A` |\n| Ocean Gradient | `065A82` | `1C7293` | `21295C` | `2B3A4E` | `6B8FAA` |\n| Teal Trust | `028090` | `00A896` | `02C39A` | `2D3B3B` | `5E8C8C` |\n| Sage Calm | `84B59F` | `69A297` | `50808E` | `2D3D35` | `7A9488` |\n| Cherry Bold | `990011` | `FCF6F5` | `2F3C7E` | `333333` | `8B6B6B` |\n\nPick by topic, not by default — finance reads Midnight Executive, a product launch reads Coral Energy, safety / LOTO reads Cherry Bold. If the closest named theme is not quite right, blend (e.g. Forest primary + gold `D4A843` accent). Use **Text** on light fills, **Muted** for captions / axis / footer, `FFFFFF` or Secondary for body on dark fills.\n\nOn dark backgrounds, text and chart series follow the Hard rules contrast floor above.\n\n### Chart-choice decision table\n\nWrong chart type kills the 3-second test:\n\n| Data shape | Use | Avoid |\n|---|---|---|\n| Category comparison (A vs B vs C) | `column` (vertical) / `bar` (≥ 6 categories, horizontal) | pie (slices merge), line (no time axis) |\n| Time series, 1–3 series | `line` | area (occlusion), bar (implies discrete) |\n| Part-of-whole, 2–5 slices | `pie` / `doughnut` | pie with 8+ slices (unreadable) |\n| Correlation / distribution | `scatter` | line (implies ordering) |\n| Multiple categories × metrics, dense | stacked `column` or heatmap | one chart per metric — consolidate |\n| KPI snapshot (single big number) | **Large-text shape** (60–72pt + ≤ 5-word sublabel), NOT a chart | gauge chart, tiny bar |\n\nRule of thumb: if > 3 series and > 8 categories, split into two charts or switch to a table.\n\n### Animation\n\nUse as much or as little as the brand and content call for — a formal finance deck trends to near-zero, a product launch can be more expressive. Animation is a tool, not décor. Three floors keep it from hurting the deck (none caps how much you use):\n\n- **Purposeful** — each one reveals or emphasizes (progressive bullet reveal, a build-up chart), never decorates. If it doesn't aid comprehension, cut it.\n- **Degrades gracefully** — pptx animation renders inconsistently across viewers (Keynote / Slides / web / mobile) and may not play at all, so every slide must read correctly as a *static* frame. Never hide essential content behind a reveal.\n- **Verify live** — animation is runtime-only; `view html` and screenshots can't see it, so confirm in a real presentation viewer before shipping.\n\nTaste steer (not a ban): `fade` / `appear` / a single `zoom-entrance` with snappy durations (~hundreds of ms) fit most decks; `bounce` / `swivel` / `spin` / `fly-from-edge` / dense multi-object choreography usually read amateur — reach for them only when the brand is deliberately playful.\n\n### Layout patterns & data display\n\nVary layout across slides — repeating the same pattern makes every slide feel identical. These are common building blocks, not the full set — pick one per slide, or build a layout outside the table when the content calls for it:\n\n| Pattern | When to use | Key measurement |\n|---|---|---|\n| **Two-column** (text left, visual right) | Concept + evidence; feature + screenshot | Each col ≈ 14-15cm; gap 1cm |\n| **Icon rows** (icon in filled circle + bold header + description) | Feature lists, benefits, team roles | Icon circle 1.5-2cm; 3-4 rows max |\n| **2×2 or 2×3 grid** (card tiles) | Quadrant analysis, SWOT, option comparison | Gap ≥ 0.76cm; consistent card height |\n| **Half-bleed image** (full left or right half, content overlay on other side) | Hero moments, case study openers | Image 16-17cm wide; content column ≥ 14cm |\n| **Large stat callout** (60-72pt number + ≤5-word sublabel below) | Single KPI, milestone, market size | Use shape, NOT a chart; sublabel 14-16pt muted |\n\n**Data display quick rules:**\n- Comparison columns (before/after, A vs B) beat a table for 2-3 options.\n- Timelines and process flows: numbered step shapes + connectors, not a bullet list.\n\n### Image treatment (only when a slide uses a photo / screenshot / logo)\n\n**Read the image first** (open the file) and choose treatment from what you see — don't place blind from a filename.\n\n- **Full-bleed photo** → size to COVER the region (crop edges), no border.\n- **Screenshot / diagram / logo** → size to FIT (never crop content). A transparent or fit image sits on a contrasting fill — drop a colored rectangle behind it, don't let it float on white.\n- **Text over a photo** → never raw on the image. Put it on a card, or lay a protection scrim between image and text (a dark rectangle at ~50–60% opacity, or a gradient fading from the text edge).\n- Never stretch (distort the aspect ratio); don't overlay text on a busy screenshot.\n- Prefer user-provided images / brand assets; no emoji or self-drawn art unless asked.\n\n### Visual motif commitment\n\nPick ONE distinctive element (rounded image frames, section numbers in filled circles, single-side border band, diagonal accent strips) and carry it to every slide — commit across the whole deck; styling one slide and leaving the rest plain reads as abandoned. A secondary motif is fine only if it doesn't compete with the primary. Declare it in your build plan first: `## Motif: numbered circles in brand color`.\n\n### Visual AI-tells to avoid\n\n- **No decorative underline under slide titles.** A stripe / rule below a heading is the single most common AI-slide tell — use whitespace or a background-color change instead.\n- **No rounded-corner card with a colored left-border accent stripe.** The other classic AI-slide tell — use a solid fill, a top accent band, or whitespace separation instead.\n- **No emoji as iconography** unless the brand uses them — use a shape or a real icon asset.\n\nCopy-level tells live in \"Copy reads human\".\n\n## Common Workflow\n\n1. **Open/save lifecycle.** `officecli open <file>` at the start, `officecli save <file>` at the end to flush your edits to disk. `save` only writes — it leaves the resident warm for any follow-up edit; reach for `officecli close <file>` only when you want to release the resident immediately (a one-shot handoff). Both are always safe — they never error or lose work. Use `batch` for repetitive shape grids. **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (python-pptx, PowerPoint, a renderer, delivery).\n2. **Orient.** New deck: `officecli create \"$FILE\"`. Existing: `officecli view \"$FILE\" outline` first. Never edit blind.\n3. **Title sequence first (plan, don't build yet).** Before creating any slide or shape, write out the full ordered list of slide titles. If someone reading ONLY the titles can't follow the argument, fix the arc now — cheaper in a list than after 14 slides. Pick ONE title grammar — all topic noun-phrases or all action statements, never a mix — and hold it throughout (see \"Copy reads human\").\n4. **Build in display order.** Add slides in audience-view order: cover → agenda → section-1 divider → section-1 content → section-2 divider → … → closing. `--index` on slide add works, but linear append keeps the build script readable and avoids index-arithmetic bugs. **Before final delivery, confirm slide count + narrative arc match your build plan.** Gate 3's order-sanity check catches cases where the cover ends up as slide 11 of 14 instead of slide 1.\n5. **Incremental per slide.** Create slide + background, then title, then supporting shapes / charts / connectors. Always `layout=blank` for custom designs. After each structural op, `get /slide[N] --depth 1` to confirm shape IDs.\n6. **Format to spec.** Per the Requirements table; formatting is deliverable, not polish.\n7. **Save + verify.** `officecli save` flushes the file to disk (or `officecli close` to flush and also end the session). Always open in the target presentation viewer before shipping — chart colors, animations, fonts, and zoom are runtime features `view html` can't render. Full verification in QA below.\n8. **QA — assume there are problems.** Fix-and-verify until a cycle finds zero new issues.\n\n## Quick Start\n\nMinimal viable deck: cover + one content slide + notes. `$FILE` stands in for your filename.\n\n```bash\nFILE=\"deck.pptx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\n# Cover — dark fill, centered title\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" /slide[1] --type shape --prop text=\"FY26 Strategic Review\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center\n\n# Content — white fill, title + body + notes\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" /slide[2] --type shape --prop text=\"Revenue grew 18% YoY\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761\nofficecli add \"$FILE\" /slide[2] --type shape --prop text=\"Enterprise renewals + new EMEA region drove the beat; NRR held at 118%.\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=30cm --prop height=3cm \\\n  --prop font=Calibri --prop size=20 --prop color=333333\nofficecli add \"$FILE\" /slide[2] --type notes --prop text=\"Lead with the 18% beat, preview EMEA.\"\n\nofficecli save \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nShape of every build: open → slide+background → title → body → notes → save → validate.\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` first, `view text` / `get` / `query` once you know where to look.\n\n```bash\nofficecli view \"$FILE\" outline          # slide count + titles\nofficecli view \"$FILE\" annotated        # complete per-slide breakdown with fonts, sizes, tables, charts\nofficecli view \"$FILE\" text --start 1 --end 5   # text dump (includes table cell text)\nofficecli view \"$FILE\" issues           # empty slides, overflow hints\nofficecli view \"$FILE\" stats            # counts + totals (incl. pictures missing alt)\n```\n\n**Inspect one element.** XPath-style paths, 1-based. ALWAYS quote. Prefer `@name=` / `@id=` selectors over positional `[N]` (stable across reorderings). `[last()]` works. Add `--json` for machine output.\n\n```bash\nofficecli get \"$FILE\" \"/slide[1]\" --depth 1              # shape list with IDs and names\nofficecli get \"$FILE\" \"/slide[1]/shape[@name=Title]\"\nofficecli get \"$FILE\" \"/slide[1]/table[1]\" --depth 3     # table rows / cells\n```\n\n**Query across the deck.** CSS-like selectors; operators `=`, `!=`, `~=`, `>=`, `<=`, `[attr]`, `:contains()`, `:no-alt`. `help pptx query` lists queryable element types.\n\n```bash\nofficecli query \"$FILE\" 'shape:contains(\"Revenue\")'\nofficecli query \"$FILE\" 'picture:no-alt'                 # accessibility gap\nofficecli query \"$FILE\" 'shape[fill=1E2761]'             # color match\nofficecli query \"$FILE\" 'shape[width>=10cm]'             # numeric\n```\n\n**`query --json` output schema.** Results wrap in `.data.results[]` — `jq -r '.data.results[0].format.id'`, NOT `.[0].id`. Shape name is `.name`; fill is `.format.fill`; textColor is `.format.textColor`.\n\n**Visual preview (LEAD).**\n\n```bash\nofficecli view \"$FILE\" html                # prints an HTML preview path; Read it for per-slide visual audit (best structural ground truth)\nofficecli view \"$FILE\" svg --start 3 --end 3   # single slide SVG (charts + gradients do NOT render in SVG)\n```\n\n**Reading the output — an expected non-defect:**\n- **`layout=blank` has no title placeholder.** Titles are plain `shape` elements, so `view outline` reporting `(untitled)` is **expected**, not a defect. Use `layout=title` + `placeholder[title]` only when screen-reader outline compatibility matters.\n\n## Creating & Editing\n\nVerbs: `add` / `set` / `remove` / `move` / `swap` / `batch` / `raw-set`. Ninety percent of a deck is slides, shapes, text, a few charts, pictures, connectors.\n\n### Slides and backgrounds\n\nA slide is `/slide[N]`. Always pass `layout=blank` for custom designs. Background: solid, gradient, or image.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761                 # solid\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop \"background=1E2761-CADCFC-180\"   # gradient (start-end-angle)\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=image:/path/to/hero.jpg  # image background (LEAD)\n```\n\n### Shapes\n\nA `shape` holds text, fill, border, position, and optional animation / link.\n\n```bash\nofficecli add \"$FILE\" /slide[2] --type shape --prop name=Title --prop text=\"Key Insight\" \\\n  --prop x=2cm --prop y=2cm --prop width=20cm --prop height=3cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n```\n\nPositioning is explicit — no layout engine, you own the grid math. `--prop preset=` picks geometry (`rect`, `roundRect`, `ellipse`, `triangle`, `arrow`, `star5`, ...); custom `M...Z` paths are not supported — pick a preset. **Name shapes at creation** (`--prop name=HeroTitle`) and address later with `\"/slide[N]/shape[@name=HeroTitle]\"` — names survive z-order / remove-then-add, whereas positional `/shape[3]` (and even `@id=`) shift. Re-`get --depth 1` after any structural change before using positional indexes.\n\n### Text inside shapes (paragraphs, runs, styling)\n\nA shape has paragraphs (`paragraph[K]`) and runs (`run[K]`). For one-line text, `--prop text=` on the shape is enough; a `\\n` in the text makes a paragraph break, `\\t` a tab (see Shell & Execution Discipline; double `\\\\n` for a literal). `add --type paragraph` takes the same style props as a shape (text, align, bold, italic, size, color, font). For mixed styling *within* a line, append a styled run:\n\n```bash\nofficecli add \"$FILE\" \"/slide[2]/shape[@name=Card1]/paragraph[1]\" --type run \\\n  --prop text=\" (inline detail)\" --prop size=14 --prop italic=true --prop color=8899BB\n```\n\n### Charts\n\nPick chart type per the Design Principles chart-choice table. Full prop list (chartType enum, `seriesN.*`, `data=`/`categories=`, axis options): `help pptx add chart`. Typical multi-series with brand colors:\n\n```bash\nofficecli add \"$FILE\" /slide[3] --type chart --prop chartType=column \\\n  --prop series1.name=Revenue --prop series1.values=\"42,45,48\" --prop series1.color=1E2761 \\\n  --prop series2.name=Growth  --prop series2.values=\"2,7,7\"    --prop series2.color=CADCFC \\\n  --prop categories=\"Q1,Q2,Q3\" \\\n  --prop x=2cm --prop y=4cm --prop width=20cm --prop height=10cm\n```\n\nGotchas: (1) chart titles with `()`, `[]`, `TBD` ship as literal text. (2) some viewers normalize chart colors to theme defaults — verify in the target viewer. Series can be added after creation (`add --type series`).\n\n### Pictures\n\n```bash\nofficecli add \"$FILE\" /slide[4] --type picture --prop src=hero.jpg \\\n  --prop x=1cm --prop y=1cm --prop width=32cm --prop height=18cm \\\n  --prop alt=\"Product hero, gradient lit from right\"\n```\n\nConfirm with `officecli query \"$FILE\" 'picture:no-alt'` — must be empty before delivery.\n\n### Connectors (LEAD — flowcharts / decision trees first-class)\n\nDraws a line between two shapes or free coordinates. Full prop / enum reference (`shape`, `headEnd`/`tailEnd` values, `from`/`to` ref forms): `help pptx add connector`.\n\n```bash\nofficecli add \"$FILE\" /slide[5] --type connector \\\n  --prop \"from=/slide[5]/shape[@name=BoxA]\" --prop \"to=/slide[5]/shape[@name=BoxB]\" \\\n  --prop shape=elbow --prop color=333333 --prop tailEnd=triangle\n```\n\n**Every flow connector needs an arrowhead.** Without one, `bentConnector3` renders as a directionless line. `preset=rightArrow` overlay only works for horizontal flows; diamonds / decision trees with diverging edges need `tailEnd=`.\n\n### Animations (LEAD)\n\nUse per the Animation floors above (purposeful, degrades gracefully, verify live). Preset names + duration syntax: `help pptx animation`.\n\n```bash\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=HeroCard]\" --prop animation=fade-entrance-400\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=HeroCard]\" --prop animation=none    # clear all\n```\n\n### Hyperlinks, tooltips, slide-jump\n\n`--prop link=slide[N]` for an in-deck jump (1-based; target slide must exist), `link=nextslide` / `firstslide` / `lastslide` / `previousslide` / `endshow` for named navigation, `link=https://...` for a URL, `--prop tooltip=\"...\"` for hover text.\n\n### Tables, placeholders, groups, zoom — one-liners\n\n- **Tables** — `--type table --prop rows=N --prop cols=M`. Row-level `set` supports `height` and `c1/c2/c3` (seed cell text). Header-row styling is table-level (`firstRow=true` / `headerFill=`), not a row prop. Cell formatting lives on the cell paragraph / run. Populate rows BEFORE setting table-level font (font cascade gets reset by row ops).\n- **Placeholders** — `\"/slide[N]/placeholder[title]\"` / `placeholder[body]`. Available only when the slide uses a layout with placeholders (not `layout=blank`).\n- **Groups** (LEAD) — address children via `\"/slide[N]/group[@name=G]/shape[1]\"`. Survives reordering better than positional indexes.\n- **Zoom slide** (LEAD) — `--type zoom --prop target=N` (one link per target; alias `slide`). Emit N separate zoom shapes for a multi-target nav hub. Zoom is a runtime feature — `view html` shows the static geometry; the zoom interaction runs only in a live presentation viewer.\n- **Slide comments** — reviewer annotations anchored at `/slide[N]/comment[M]`. Full lifecycle (`add / set / get / query / remove`). Props: `text`, `author`, `initials` (auto-derived), `date` (ISO 8601, defaults to UtcNow), `x` / `y` (length anchor).\n  ```bash\n  officecli add \"$FILE\" \"/slide[2]\" --type comment --prop author=\"Alice\" --prop text=\"Tighten this bullet\" --prop x=20cm --prop y=3cm\n  officecli query \"$FILE\" 'comment' --json | jq '.data.results | length'   # count all review comments\n  officecli remove \"$FILE\" \"/slide[2]/comment[1]\"                           # resolve after addressing\n  ```\n\n### Deck-level recipes\n\nPatterns not obvious from the primitives. Each gives the **visual outcome** first, then a runnable block. `$FILE` = your filename. Use `/slide[last()]` to address the slide you just added. The recipes demonstrate **structure and coordinate math** — swap in the palette / fonts you chose for this topic; the navy `1E2761` + Georgia is just the example's theme, not a house style to copy verbatim.\n\n**Z-order.** Later-added shapes are on top. Add background decoration FIRST, titles LAST. To fix after the fact: `--prop zorder=back/front` (renumbers siblings — re-`get --depth 1` before stacking more).\n\n#### (a) Cover (and section divider)\n\n**Visual outcome.** Dark navy fill, centered 44pt title, 18pt ice-blue meta line.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Strategic Growth Review\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Prepared for Acme Leadership — FY26 Outlook\" \\\n  --prop x=2cm --prop y=11cm --prop width=29.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=18 --prop color=CADCFC --prop align=center\n```\n\n**Section divider** = same cover, plus a giant translucent number (`size=120`, `opacity=0.15`) added FIRST so it sits behind the section title.\n\n#### (b) Data slide (chart + commentary block)\n\n**Visual outcome.** Left two-thirds: column chart with brand series colors. Right one-third: \"Key Insight\" card with 20pt heading + 18pt body — audience reads the takeaway before parsing the bars.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"FY26 Revenue Beat Plan by 18%\" \\\n  --prop x=1.5cm --prop y=1cm --prop width=30cm --prop height=1.8cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761\n\n# Chart — left 2/3 (single-quote the title because of `$`)\nofficecli add \"$FILE\" \"/slide[last()]\" --type chart --prop chartType=column \\\n  --prop series1.name=Actual --prop series1.values=\"42,45,48,55\" --prop series1.color=1E2761 \\\n  --prop series2.name=Plan --prop series2.values=\"40,42,45,48\" --prop series2.color=CADCFC \\\n  --prop categories=\"Q1,Q2,Q3,Q4\" --prop x=1.5cm --prop y=3.5cm --prop width=20cm --prop height=14cm --prop title='FY26 Revenue ($M)'\n\n# Commentary card — right 1/3: background + heading + body\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop preset=roundRect --prop fill=F5F7FA --prop line=none \\\n  --prop x=22.5cm --prop y=3.5cm --prop width=9.8cm --prop height=14cm\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Key Insight\" \\\n  --prop x=23cm --prop y=4cm --prop width=9cm --prop height=1.2cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"EMEA launch + NRR at 118% drove 12pp of the 18pp beat.\" \\\n  --prop x=23cm --prop y=5.5cm --prop width=9cm --prop height=11cm \\\n  --prop font=Calibri --prop size=18 --prop color=333333\n```\n\n#### (c) Flowchart / process diagram (boxes + connectors)\n\n**Visual outcome.** Four rounded boxes across at y=8cm, each 6×3cm, alternating navy/iceblue, joined by elbow connectors with triangle arrowheads.\n\nGrid math (4 boxes, 33.87cm slide, 1.5cm margins): `gap = (33.87 − 3 − 24) / 3 = 2.29cm`. x-positions: `1.5, 9.79, 18.08, 26.37`.\n\nEach box carries its own label via `valign=middle` (no separate overlay shape needed). Use `batch` heredoc for portable coordinate arithmetic — no `bc`, no bash arrays.\n\n```bash\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step1\",\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"1.5cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 1\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step2\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"9.79cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 2\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step3\",\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"18.08cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 3\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step4\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"26.37cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 4\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}}\n]\nEOF\n\n# Connector pattern — reuse for any box-to-box graph.\nfor pair in \"Step1 Step2\" \"Step2 Step3\" \"Step3 Step4\"; do\n  A=${pair% *}; B=${pair#* }\n  officecli add \"$FILE\" \"/slide[$SLIDE]\" --type connector \\\n    --prop \"from=/slide[$SLIDE]/shape[@name=$A]\" \\\n    --prop \"to=/slide[$SLIDE]/shape[@name=$B]\" \\\n    --prop shape=elbow --prop color=333333 --prop tailEnd=triangle\ndone\n```\n\n`shape=elbow` is canonical (`bentConnector2` / `bentConnector3` also accepted).\n\n#### (d) Multi-slide deck skeletons\n\nNo code block — it's a rhythm. The sequences below are **illustrations of one working cadence (alternating dark dividers with white content), not required running orders** — derive your actual arc from the content first (see \"Title sequence first\"), then borrow whatever divider/content rhythm fits:\n\n- **10-slide review:** Cover · Agenda · 3 KPI · Div01 · Chart · Chart · Div02 · Flow · Timeline · Close\n- **20-slide pitch:** same rhythm × 2, sectioned Problem · Solution · Market · Product · Traction · Model · Team · Financials · Ask\n- Every divider must appear **before** its section content (Gate 3 order sanity)\n- Cover/divider = (a); chart pages = (b); process pages = (c); KPI pages = (e); decision pages = (f)\n\n#### (e) KPI callouts — giant-number card grid\n\n**Visual outcome.** Three or four giant numbers across a row; each card = unit sublabel + small percent-change chip + one-line takeaway. The single most common exec-deck element.\n\n**Sizing rule.** 60pt Georgia bold fits ~5 chars in a 9.78cm card (`$84.2`, `118%`, `24.5`). For longer values (`$84.2M`), split: `$84.2` as the big number, `USD millions` as the sublabel — never shrink the font to chase a unit suffix, it just wraps.\n\nGrid math (3 cards, 1.5cm margins, 0.76cm gap): `col_width = (33.87 − 3 − 1.52) / 3 = 9.78cm`. x-positions: `1.5, 12.04, 22.58`. Use accent color on a single \"watch\" card so risk reads in one second.\n\n```bash\n# Two cards: navy standard + terracotta watch. Each = bg + big number + sublabel + chip.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"1.5cm\",\"y\":\"4cm\",\"width\":\"9.78cm\",\"height\":\"7cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"84.2\",\"x\":\"1.5cm\",\"y\":\"4.8cm\",\"width\":\"9.78cm\",\"height\":\"2.8cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"USD millions · ARR\",\"x\":\"1.5cm\",\"y\":\"8cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"color\":\"CADCFC\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"+24% YoY\",\"x\":\"1.5cm\",\"y\":\"9cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"CADCFC\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"preset\":\"roundRect\",\"fill\":\"B85042\",\"line\":\"none\",\"x\":\"22.58cm\",\"y\":\"4cm\",\"width\":\"9.78cm\",\"height\":\"7cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"\\$1.42\",\"x\":\"22.58cm\",\"y\":\"4.8cm\",\"width\":\"9.78cm\",\"height\":\"2.8cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"CAC payback (yrs)\",\"x\":\"22.58cm\",\"y\":\"8cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"+8% — watch\",\"x\":\"22.58cm\",\"y\":\"9cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}}\n]\nEOF\n```\n\n#### (f) Decision tree — YES/NO branching\n\n**Visual outcome.** Diamond at top-center; YES/NO child boxes diverging left-right; both converge into a shared terminal box. Layout: diamond at `x=13.94, y=2cm, 6×3cm`; YES at `3cm, 7.5cm`; NO at `22.87cm, 7.5cm`; terminal at `13.94cm, 13cm`. Convention: red = stop/escalate, blue = standard, green = safe terminal. **Every connector needs an arrowhead** — readers misparse direction otherwise.\n\n```bash\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Decide\",\"preset\":\"diamond\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"13.94cm\",\"y\":\"2cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Hazardous energy present?\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"YesBox\",\"preset\":\"roundRect\",\"fill\":\"B85042\",\"line\":\"none\",\"x\":\"3cm\",\"y\":\"7.5cm\",\"width\":\"8cm\",\"height\":\"3cm\",\"text\":\"Lockout + Tagout + Verify\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"NoBox\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"22.87cm\",\"y\":\"7.5cm\",\"width\":\"8cm\",\"height\":\"3cm\",\"text\":\"Proceed with standard PPE\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Done\",\"preset\":\"roundRect\",\"fill\":\"2C5F2D\",\"line\":\"none\",\"x\":\"13.94cm\",\"y\":\"13cm\",\"width\":\"6cm\",\"height\":\"2.5cm\",\"text\":\"Begin service\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}}\n]\nEOF\n```\n\nThen 4 connectors (`Decide→YesBox`, `Decide→NoBox`, `YesBox→Done`, `NoBox→Done`) using the connector loop pattern from (c).\n\n## QA (Required)\n\n**Assume there are problems.** First render is almost never correct. If you found zero issues, you were not looking hard enough.\n\n### Delivery Gate (any failure = REJECT, do NOT deliver)\n\nGates 1–2b are text/schema-level (cannot see a rendered slide); Gate 3 is the only visual check. Done = every gate PASS **and** Gate 3 loop converged.\n\nEach gate is **run a command, judge its output** — the officecli commands are identical on every OS (macOS / Linux / Windows), so no shell scripting is needed; the judging is yours.\n\n- **Gate 1 — schema.** `officecli validate \"<file>\"`. Any schema error → REJECT and fix.\n- **Gate 2 — overflow / format / structure.** `officecli view \"<file>\" issues`. If it lists *any* issue (lines tagged `[O1]`, `[C1]`, `[S1]`, …) → REJECT, fix, re-run until clean.\n- **Gate 2b — leftover placeholders.** `officecli view \"<file>\" text`, then scan the output for `xxxx`, `lorem` / `ipsum`, `<TODO>`, `placeholder`, \"this slide layout\", or empty `()` / `[]`. Any hit → REJECT.\n\n### Gate 3 — Visual audit (MANDATORY)\n\nPick **one** path:\n\n**Screenshot (default)** — for vision-capable agents. Screenshot each slide in turn — `officecli view \"<file>\" screenshot --page 1 -o slide1.png`, then `--page 2`, … — until the page index runs past the deck (one screenshot = one slide). If it errors on page 1, use the fallback below.\n\n**Judge every PNG against the checklist, adversarially** — \"assume problems exist; finding none means you didn't look hard enough.\" Report one `slide N: <issue>` line per problem, or `PASS`. This step is required however you run it. **If** your harness can spawn a subagent, delegate the judging to a *fresh, independent* one — the agent that built the deck is biased toward \"looks fine\", a separate pair of eyes is more critical — handing it the screenshots + this checklist and the same adversarial framing. No subagent? Do exactly the same yourself.\n\n**Fallback — HTML-text** (no vision, or screenshot failed): read `view \"$FILE\" html` as text. DOM cannot prove **dark-on-dark / fine overlap / arrowheads / gap-margin metrics / column alignment** — flag these as \"not visually verified\" rather than PASS.\n\n**Optional `--grid N`** — only on user request for layout-rhythm, or when `view outline` shows anomalous layout distribution: `officecli view \"<file>\" screenshot --grid 3 -o grid.png`.\n\n**Per-slide checklist (assume issues exist):**\n\n- **overlap** — shapes / charts / giant decorative numbers (01/02/03 100pt+) colliding\n- **text overflow** — clipped at slide or shape boundary (KPI cards, narrow boxes)\n- **narrow text box** — content fits technically but wraps to many short lines (1–2 words each); long sublabel in a 3cm KPI card, body line in a too-tight column\n- **dark-on-dark** — fill brightness < 30% with text/icon brightness < 80% (incl. dark icons on dark without a contrasting circle)\n- **image treatment** — photo stretched/distorted, text raw on a busy image (no card/scrim), screenshot or logo cropped, transparent image floating on white\n- **missing arrowheads** — flowchart connectors as plain lines\n- **decorative-line / title mismatch** — accent bar sized for one-line title but title wrapped to two (or vice versa)\n- **footer / citation collision** — source line, page number, or footnote touching content above\n- **tight margin / gap** — element within ~0.5\" of slide edge, or two cards within ~0.3\"\n- **uneven gaps** — large empty area on one side, cramped on another (broken rhythm)\n- **column / repeat-element misalignment** — KPI cards / icons off baseline or inconsistent width\n- **order sanity** — sequence matches narrative (cover → agenda → dividers-before-sections → closing)\n\nREJECT with `slide N: <issue>` lines, else \"Gate 3 PASS\" (HTML-text fallback adds \"<unverified-items> not visually verified\").\n\n**Fix-verify (mandatory, max 3 cycles).** Fix → re-run Gate 3 → repeat until zero new issues; one fix often surfaces another. After 3 rounds without convergence, **stop** — likely seesaw, template-level cause, or agent misread. Report `slide N: <issue> — attempted: <fixes> — likely root: <template|design-conflict|ambiguous>` and let the user decide.\n\n**Then flush (part of the gate).** Once Gate 3 converges, end with `officecli save \"<file>\"` — this guarantees your edits are written to disk before delivery (use `officecli close \"<file>\"` instead to also release the resident on a one-shot handoff). Required final step, not optional. Always safe: never errors or loses work.\n\n## Common Pitfalls\n\nSanity-check cheatsheet — what breaks on the first try. Design + shell traps.\n\n| Pitfall | Correct approach |\n|---|---|\n| Unquoted `[N]` in zsh/bash | Always quote paths: `\"/slide[1]\"`. zsh globs unquoted `[1]` → `no matches found` — #1 first-use stumble |\n| `--name \"foo\"` | All attributes go through `--prop`: `--prop name=\"foo\"` |\n| `/shape[myname]` (bare name in brackets) | Use `@name=` selector: `/shape[@name=myname]` or `/shape[@id=10007]` |\n| Paths 1-based vs `--index` 0-based | `/slide[1]` = first slide; `--index 0` = first position |\n| `$` in `--prop text=` | Single-quote: `--prop text='$15M'`. Double-quoted `\"$15M\"` gets shell-expanded to `M` |\n| `\\n` / `\\t` in `--prop text=` | Interpreted by the CLI: `\\n` = paragraph break, `\\t` = tab. Double `\\\\n` for a literal |\n","skills/officecli-word-form/SKILL.md":"---\nname: officecli-word-form\ndescription: \"Use this skill to create fillable Word forms (.docx) with real Content Controls (SDT) + legacy FormField checkboxes + MERGEFIELD mail-merge placeholders + document protection. Trigger on: 'fillable form', 'form fields', 'content controls', 'SDT', 'word form', 'fill in', 'only editable fields', 'protect document', 'onboarding form', 'HR intake', 'survey template', 'contract / SOW template', 'mail-merge template', 'compliance checklist', 'medical intake questionnaire'. Output is a single .docx where specific fields are editable and the rest is locked. This skill is INDEPENDENT, not a scene layer on docx — payload is `w:sdt` + `w:ffData` + `w:fldChar` + `documentProtection`, none of which docx base skill covers. Do NOT trigger for regular reports, letters, memos, academic papers, pitch decks, or any document with no user-fillable fields — route those to officecli-docx or its scene layers.\"\n---\n\n# OfficeCLI Word-Form Skill\n\n**This skill is INDEPENDENT, not a scene layer on docx.** A form's payload — `<w:sdt>` controls, `<w:ffData>` legacy fields, `<w:fldChar>` mail-merge, `documentProtection` — is a distinct element class from docx's paragraph/heading/style primitives. Its QA is different too: docx's Delivery Gate cares about visual layout and live PAGE fields, this skill's cares about data plumbing (protection enforced / alias+tag / items injected / name ≤ 20 / no underscore anti-pattern). **Reverse handoff:** if the user's document has no fillable fields (report, letter, memo, thesis, proposal), route to `officecli-docx` or a docx scene skill — don't use this one.\n\n## BEFORE YOU START (CRITICAL)\n\n**If `officecli` is not installed:**\n\n`macOS / Linux`\n\n```bash\nif ! command -v officecli >/dev/null 2>&1; then\n    curl -fsSL https://d.officecli.ai/install.sh | bash\nfi\n```\n\n`Windows (PowerShell)`\n\n```powershell\nif (-not (Get-Command officecli -ErrorAction SilentlyContinue)) {\n    irm https://d.officecli.ai/install.ps1 | iex\n}\n```\n\nVerify: `officecli --version`\n\nIf `officecli` is still not found after first install, open a new terminal and run the verify command again.\n\nIf the install command above fails (e.g. blocked by security policy, no network access, or insufficient permissions), install manually — download the binary for your platform from https://github.com/iOfficeAI/OfficeCLI/releases — then re-run the verify command.\n\n## Help-First Rule\n\nThis skill teaches what a real form needs, not every CLI flag. When a prop / alias / enum is uncertain, consult help BEFORE guessing: `officecli help docx [element] [--json]` (e.g. `sdt`, `formfield`, `field`). Help is pinned to the installed CLI version and is authoritative — when this skill and help disagree, **help wins** (the prop set on `sdt` in particular has grown over time; trust `help docx sdt`, not a hardcoded list).\n\n## Mental Model & Inheritance\n\nA Word form is a `.docx` plus four OpenXML payload layers plain-docx skills do not touch: **`<w:sdt>`** content controls (types: text / richtext / dropdown / combobox / date / picture / group), **`<w:ffData>`** legacy FormField (still the only way to get a real checkbox — SDT `type=checkbox` is not implemented), **`<w:fldChar>`** complex fields (MERGEFIELD, REF, PAGEREF, SEQ, IF — template-time, not user-fill), and **`documentProtection`** (the lock that makes non-field text read-only in Word — and, on the CLI, `protection=forms` locks non-field content edits (those need `--force` or `raw-set`) but still allows form-field (SDT) edits, which is the point of forms protection).\n\n**No inheritance from docx v2.** docx's Delivery Gate (cover-fill %, live-PAGE check) does NOT apply — form QA is `view forms` + `query sdt alias+tag` + `protectionEnforced`.\n\n**Reverse handoff to docx.** Route back to `officecli-docx` for reports / letters / memos / thesis / pitch decks / any document with no editable fields. Use **this** skill when the document's purpose is data capture or template merge.\n\n## Shell & Execution Discipline\n\n**One command at a time. Read output before the next.** OfficeCLI is incremental — every `add` / `set` / `remove` immediately mutates the file. All recipes below use `FILE=form.docx` as a shell variable.\n\n**Three shell-escape layers:**\n\n1. **Quote every path with `[N]`** — zsh/bash glob-expand brackets. `officecli get \"$FILE\" /body/sdt[1]` fails with `no matches found`. Correct: `officecli get \"$FILE\" '/body/sdt[1]'`.\n2. **Single-quote any prop containing `$`** — `\"Total: $50,000\"` becomes `\"Total: ,000\"` after `$50` variable expansion. Correct: `'Total: $50,000'`.\n3. **`--after find:<text>` uses outer single quotes, never inner double quotes** — `--after find:\"Client Signature:\"` makes the quotes part of the search string; match fails. Correct: `--after 'find:Client Signature:'`.\n\n**`WARNING: UNSUPPORTED` (exit 2) is a silently-wrong element.** The CLI created the element *without* the rejected prop (exit 2 = ran with caveats; only when *every* prop is refused does `--json` report `success: false`). Any UNSUPPORTED in your build log means a prop name the current CLI does not accept on that element — stop, check `help docx <element>` for the right prop name (most SDT props such as `items`/`format`/`lock` ARE accepted now; `maxlength` is not), fix the command, and re-run. Do not ship on top.\n\n**`protection=forms` is the LAST structural command.** Once it is set, `protection=forms` locks non-field content (those edits need `--force` or `raw-set`) but still allows form-field (SDT) edits — which is the point of forms protection. A non-field content edit (e.g. `set /body/p[N] --prop text=`) is refused (`ERROR: Document is protected … use --force`); a form-field edit (e.g. `set /body/sdt[N] --prop text=` / `--prop alias=`) succeeds (exit 0). Use `Query(\"editable\")` to find fillable fields. So finish all non-field (static layout) edits first, then lock; if you must edit static content afterward, pass `--force` (or temporarily clear protection, edit, re-lock).\n\n### `--after find:` micro-playbook\n\n`--after find:<text>` matches the **first** occurrence. Bad anchor = wrong insertion location, expensive to debug. Three rules:\n\n1. **Anchor must be globally unique.** In bilingual contracts \"甲方签字\" matches both parties — use a unique phrase like \"甲方签字（Service Provider）\" or full English title.\n2. **After insert, `/body/p[last()]` is unreliable** — the find insertion changes `<w:body>` child order. To continue operating on the new paragraph, read its real paraId: `officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId'`.\n3. **Chinese + full-width parens `（）`** match literally in `find`, but when unsure, `officecli view \"$FILE\" text | grep -n \"锚点\"` first to confirm the exact bytes in the file.\n\n```bash\n# Trap: first-match hits 甲方 only, 乙方 missed\nofficecli add \"$FILE\" /body --type sdt --after 'find:签字'\n\n# Fix: two signatories, two unique anchors\nofficecli add \"$FILE\" /body --type sdt --prop alias=Party_A_Name --prop tag=party_a \\\n  --after 'find:甲方签字（Service Provider）'\nPID_A=$(officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId')\nofficecli add \"$FILE\" \"/body/p[@paraId='$PID_A']\" --type sdt --prop alias=Party_A_Title --prop tag=party_a_title\n```\n\nInline SDT via `--after find:` is added as a child of the matched paragraph, not as a new paragraph — use this when label + SDT must share a line.\n\n## What makes a real form (identity)\n\nA real fillable form requires **structured fields** + **document protection**.\n\n| Approach | Word user sees | CLI-readable | Real form? |\n|---|---|---|---|\n| SDT controls + `protection=forms` | Gray-bordered fields; rest locked | `query sdt` / `view forms` | **YES** |\n| FormField checkbox + `protection=forms` | Real clickable checkbox; rest locked | `query formfield` / `view forms` | **YES** (checkbox only) |\n| MERGEFIELD placeholders | `«CustomerName»` merged by downstream engine | `query field` | **YES** (template-time) |\n| Underscores `___` / blank lines | Visual-only; whole doc editable | No — no structured fields | **NO** |\n\n**Do not simulate fields with underscores.** `姓名：_______________` produces zero structured data and leaks past every verification. Always use `--type sdt` or `--type formfield`.\n\n**Checkbox is formfield, NOT SDT.** `--type sdt --prop type=checkbox` exits 1 (`SDT type 'checkbox' is not implemented`). Every checkbox in every recipe uses `--type formfield --prop type=checkbox`.\n\n**MERGEFIELD is a separate track.** `view forms` lists SDT + formfield only; `query field` lists complex fields only. Two disjoint inventories; both valid in one file.\n\n## Requirements for Outputs (hard floor)\n\nEvery form must satisfy these — Delivery Gate enforces each as an executable check.\n\n1. `protection=forms` enforced (`get $FILE /` → `protectionEnforced=True`).\n2. Every SDT has both `alias` + `tag`.\n3. Every dropdown/combobox has non-empty `items=...` in `view forms`.\n4. Every date SDT shows the intended `format=...`.\n5. Every locked SDT shows `lock=sdtLocked` / `contentLocked` / `sdtContentLocked` as intended.\n6. Zero `WARNING: UNSUPPORTED` in build log.\n7. Zero `type=checkbox` on any SDT.\n8. Every formfield `name` ≤ 20 characters.\n9. Zero underscore-line / blank-line placeholders.\n10. Field types match user intent (short text / paragraph / fixed list / list+custom / date / boolean).\n\n## Three Paths (core decision)\n\nSDT props are **first-class on `add`** — confirm the current set with `officecli help docx sdt`. Today that includes `type, tag, alias, text, items, format, lock, placeholder/placeholderText, date.*` and the SDT types `text / richtext / dropdown / combobox / date / group / picture`. So most forms are pure `add` — no raw-set, no Word template. Two paths remain only for the genuinely-unreachable cases. **Pick the path before writing a single command.**\n\n### Path A — Pure CLI (the default for almost everything)\n\n**Use when**: any text / richtext / dropdown / combobox / date / picture / group SDT — including its options, date format, and lock. Pass the props straight on `add`; verify each persists with `get '/body/sdt[N]' --json`.\n\n```bash\n# dropdown WITH its options and a non-default date format, in one add each — no raw-set:\nofficecli add \"$FILE\" /body --type sdt \\\n  --prop type=dropdown --prop alias=\"Department\" --prop tag=dept \\\n  --prop items=\"Engineering,Finance,HR\"\nofficecli add \"$FILE\" /body --type sdt \\\n  --prop type=date --prop alias=\"Start Date\" --prop tag=start \\\n  --prop format=\"yyyy年MM月dd日\"\n# lock works on add AND set:\nofficecli add \"$FILE\" /body --type sdt --prop type=text --prop tag=full_name \\\n  --prop alias=\"Full Name\" --prop text=\"Enter full name\" --prop lock=sdtLocked\n# protection comes last, once all fields exist:\n# officecli set \"$FILE\" / --prop protection=forms\n```\n\n### Path B — CLI + `raw-set` bridge (only an attribute help does NOT expose)\n\n**Use when**: you need an SDT attribute that `help docx sdt` does not list (e.g. a `<w:listItem>` whose `w:value` must differ from its display text, or a sdtPr child with no `--prop`). `raw-set` is OfficeCLI's universal OpenXML fallback — `officecli --help` lists it as a top-level command. For ordinary dropdowns use `--prop items=` (Path A); raw-set here is the exception, not the rule.\n\n```bash\n# Display text ≠ stored value — not expressible via items=, so inject the listItems directly:\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown --prop alias=\"Department\" --prop tag=dept\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"ENG\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"FIN\"/>'\n```\n\n### Path C — Word template (only what no API reaches)\n\n**Use when**: a **real SDT checkbox** (`type=checkbox` still exits 1 — use a legacy FormField instead, see §Legacy FormField), a `placeholderDocPart` prompt-text part, or custom richtext appearance / cross-part nesting beyond `--prop` reach. Picture and grouped SDTs are NO LONGER here — they add fine via Path A.\n\n```bash\n# One-time in Word: Developer tab → Insert Content Control → Save as template.docx\ncp templates/onboarding_with_signature.docx \"$FILE\"\nofficecli open \"$FILE\"\nofficecli view \"$FILE\" forms                 # inspect embedded controls + paths\nofficecli set \"$FILE\" '/body/sdt[@sdtId=3]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" / --prop protection=forms\n```\n\n### Decision table\n\n| Need | Path | Note |\n|---|---|---|\n| text / richtext SDT with default string | **A** | `--prop type/alias/tag/text` |\n| text SDT that must be locked | **A** | `--prop lock=sdtLocked` works on `add` (and `set`) |\n| dropdown / combobox **with options** | **A** | `--prop items=\"A,B,C\"` |\n| date SDT with non-default format | **A** | `--prop format=\"yyyy年MM月dd日\"` |\n| signature picture SDT, grouped SDT | **A** | `--prop type=picture` / `type=group` add directly |\n| dropdown whose stored value ≠ display text | **B** | raw-set append `<w:listItem w:value=…>` |\n| real checkbox | **FormField** | `--type formfield --prop type=checkbox` (see §Legacy FormField) |\n| mail-merge placeholder | **MERGEFIELD** | `--type field --prop fieldType=mergefield` (see §MERGEFIELD) |\n| real SDT checkbox / placeholder part / custom appearance | **C** | build skeleton in Word, fill via CLI |\n\n## Quick Start — Path A + FormField (minimal intake form)\n\nTwo SDT text fields, one checkbox, protection. Paste and adapt; this is the smallest form worth shipping.\n\n```bash\nFILE=intake.docx\nofficecli close \"$FILE\" 2>/dev/null; rm -f \"$FILE\"   # preflight: clear stale resident / prior file (cold-start after CLI upgrade commonly leaks a resident)\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\nofficecli set \"$FILE\" / --prop title=\"Employee Onboarding Intake\" \\\n  --prop docDefaults.font=\"Calibri\" --prop docDefaults.fontSize=\"12pt\"\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Employee Onboarding Intake\" --prop style=Heading1 \\\n  --prop size=20 --prop bold=true --prop spaceAfter=18pt\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Full Name:\" --prop size=11 --prop bold=true --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Full Name\" --prop tag=full_name --prop text=\"Enter full name\"\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Start Date:\" --prop size=11 --prop bold=true --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Start Date\" --prop tag=start_date\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Read and agree to employee handbook\" --prop size=11 --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type formfield \\\n  --prop type=checkbox --prop name=agree_handbook --prop checked=false\n\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\nofficecli view \"$FILE\" forms\n```\n\n## Path B — raw-set recipes\n\nThree recipes cover almost every complex-attr need on SDT forms.\n\n### B1 — Dropdown items (append)\n\n```bash\n# Skeleton (Path A)\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown \\\n  --prop alias=\"Department\" --prop tag=dept\n\n# Inject items\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"Engineering\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"Finance\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"HR\" w:value=\"HR\"/>'\n\n# Verify\nofficecli get \"$FILE\" '/body/sdt[1]'   # expect: type=dropdown items=Engineering,Finance,HR\n```\n\n**Template.** Swap `<TAG>` / `<LABEL>` / `<VALUE>` only. `xmlns:w=...` is required on every root `<w:listItem>` — raw-set does not inherit namespace prefixes. Chain multiple `<w:listItem>`s in one call; option order is preserved.\n\n### B2 — Combobox items (same as B1, different xpath tail)\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=combobox \\\n  --prop alias=\"Current Medication\" --prop tag=current_med\n\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='current_med']/w:sdtPr/w:comboBox\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Antihypertensives\" w:value=\"Antihypertensives\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Insulin\" w:value=\"Insulin\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Other (specify)\" w:value=\"Other\"/>'\n```\n\nOnly difference from B1: `w:comboBox` vs `w:dropDownList` in the xpath tail. Combobox lets the user type custom input; dropdown does not.\n\n### B3 — Date format (setattr)\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Contract Start Date\" --prop tag=contract_start\n\n# Chinese: yyyy年MM月dd日\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='contract_start']/w:sdtPr/w:date/w:dateFormat\" \\\n  --action setattr \\\n  --xml \"w:val=yyyy年MM月dd日\"\n\n# US:    w:val=MM/dd/yyyy\n# ISO:   w:val=yyyy-MM-dd  (already the default)\n# Long:  w:val=\"MMMM d, yyyy\"\n\nofficecli get \"$FILE\" '/body/sdt[N]'   # expect: type=date format=yyyy年MM月dd日\n```\n\n`setattr` replaces one attribute — do not quote the value inside `--xml`. Only `w:val` is touched; the `<w:dateFormat>` wrapper is preserved.\n\n### raw-set actions & errors\n\n| `--action` | Form use |\n|---|---|\n| `append` | Insert new child at end of target (B1, B2 — listItem) |\n| `setattr` | Change one attribute; `--xml \"key=value\"` (B3 — dateFormat/@val) |\n| `replace` | Replace entire target (rare — reset a full `<w:date>` wrapper) |\n| `remove` | Delete the target (clear options before re-populate) |\n\n| Symptom | Fix |\n|---|---|\n| `raw-set: 0 element(s) affected` | XPath did not match. Check the `tag` value and whether the SDT is block or inline. Fall back to `officecli raw $FILE /document` to read the real XML. |\n| `Error: prefix 'w' is not defined` | Missing `xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"` on the fragment — every root element in `--xml` needs it. |\n| Items readback empty after append | `<w:dropDownList/>` must already exist (Path A `type=dropdown` ensures this). If absent, append has nowhere to insert. |\n| `VALIDATION: N new error(s) introduced` on same line as success | Your append introduced a schema-invalid child. Treat as stop-and-fix even though `raw-set` exits 0. |\n\n## Path C — Word template workflow\n\nFor fields CLI cannot express (signature `picture` SDT, real SDT checkbox, `placeholderDocPart` prompt text, grouped SDTs, custom richtext styling), build the skeleton once in Word, then fill via CLI.\n\n**One-time in Word:** File → Options → Customise Ribbon → Developer. Developer tab → Insert Picture / Check Box / Grouping Content Control → right-click → Properties → set Title (`alias`) + Tag. Save as `template.docx`.\n\n**Fill via CLI:**\n\n```bash\ncp templates/onboarding_with_signature.docx \"$FILE\"\nofficecli open \"$FILE\"\nofficecli view \"$FILE\" forms                                    # see /body/... paths + sdtId values\nofficecli set \"$FILE\" '/body/sdt[@sdtId=3]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\n```\n\n## MERGEFIELD (data-driven track)\n\n`help docx field` declares a `fieldType` enum of ~30 values including `mergefield`, `ref`, `pageref`, `seq`, `if` — all CLI-expressible with their typed props. MERGEFIELD coexists with SDT in the same file but is reported by `query field` only; `view forms` does NOT list MERGEFIELDs (they are not user-fillable).\n\n**Canonical MERGEFIELD:**\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Dear \"\nofficecli add \"$FILE\" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CustomerName\nofficecli add \"$FILE\" '/body/p[1]' --type run --prop text=\", \"\nofficecli add \"$FILE\" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CompanyName\n# Readback: \"Dear «CustomerName», «CompanyName»\"\n```\n\n**Element-type shortcut** (equivalent): `officecli add \"$FILE\" '/body/p[1]' --type mergefield --prop name=CustomerName`.\n\n### Common field patterns\n\n| Pattern | Call shape |\n|---|---|\n| Mail-merge placeholder | `--type field --prop fieldType=mergefield --prop name=<FieldName>` |\n| Mail-merge with numeric picture (money, percent) | `--type field --prop fieldType=mergefield --prop name=Amount --prop instr='MERGEFIELD Amount \\# \"#,##0.00\"'`. The typed `format` prop is ignored for mergefield (prints a warning) — use `instr` (alias `instruction`) to embed the full field code. Verify: `query \"$FILE\" field --json \\| jq '.data.results[].format.instruction'` must contain `\\#` and the picture. |\n| Mail-merge with date picture | `--type field --prop fieldType=mergefield --prop name=StartDate --prop instr='MERGEFIELD StartDate \\@ \"yyyy-MM-dd\"'` |\n| Cross-reference to bookmark text | `--type field --prop fieldType=ref --prop name=<BookmarkName>` |\n| Cross-reference to bookmark's page number | `--type field --prop fieldType=pageref --prop name=<BookmarkName>` |\n| Auto-numbering (Figure 1 / 2 / 3) | `--type field --prop fieldType=seq --prop identifier=Figure` |\n| Page number in footer | `--type field --prop fieldType=page` |\n| \"Page X of Y\" | two fields: `fieldType=page` + `fieldType=numpages` |\n| Conditional text | `--type field --prop fieldType=if --prop expression='{ MERGEFIELD Gender } = \"Male\"' --prop trueText=\"Mr.\" --prop falseText=\"Ms.\"` |\n\n### IF conditional (CLI-expressible)\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"\"\nofficecli add \"$FILE\" '/body/p[last()]' --type field --prop fieldType=if \\\n  --prop expression='{ MERGEFIELD Gender } = \"Male\"' \\\n  --prop trueText=\"Mr.\" --prop falseText=\"Ms.\"\nofficecli add \"$FILE\" '/body/p[last()]' --type run --prop text=\" \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field --prop fieldType=mergefield --prop name=LastName\n# Merge-time result: \"Mr. «LastName»\" or \"Ms. «LastName»\"\n```\n\nNested wrappers like `{ IF { MERGEFIELD X } = \"Y\" { REF bm } \"fallback\" }` are not expressible via `--prop` chaining — drop to raw-set a hand-crafted `<w:fldChar>` / `<w:instrText>` fragment, or build once in a Word template (Path C).\n\n**Readback.** `query $FILE field` lists `/field[N]` + instruction + `fieldType`. `view $FILE forms` does NOT list MERGEFIELDs (only SDT + formfield) — they are template-time, not end-user fillable. `get $FILE '/body/p[1]'` renders the guillemet-wrapped field name.\n\n## Legacy FormField\n\nUse FormField **when you need a real checkbox**. For text/dropdown, prefer SDT.\n\n`help docx formfield`: `type` (text/checkbox/check/dropdown), `name` (required, **≤ 20 chars** — OpenXML schema MaxLength; add passes longer but `validate` rejects), `text` (text only, alias `value`), `checked` (checkbox only).\n\n```bash\n# CHECKBOX — the only real checkbox (SDT type=checkbox is not implemented)\nofficecli add \"$FILE\" /body --type formfield --prop type=checkbox \\\n  --prop name=agree_terms --prop checked=false\n\n# TEXT formfield\nofficecli add \"$FILE\" /body --type formfield --prop type=text \\\n  --prop name=emp_name --prop text=\"Enter name\"\n\n# DROPDOWN formfield — items NOT settable via CLI; use Word template or SDT Path B\nofficecli add \"$FILE\" /body --type formfield --prop type=dropdown --prop name=dept_select\n\n# Read / modify by name (stable) or 1-based index\nofficecli get \"$FILE\" '/formfield[agree_terms]'\nofficecli set \"$FILE\" '/formfield[agree_terms]' --prop checked=true\nofficecli set \"$FILE\" '/formfield[emp_name]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" '/formfield[dept_select]' --prop text=\"Engineering\"\n```\n\nFormField paths (`/formfield[N]` or `/formfield[<name>]`) are separate from SDT paths (`/body/sdt[N]`). Both coexist; `protection=forms` covers both.\n\n**Scale.** Tested with 50+ checkboxes in a single document — no practical cap on formfield count; build and `validate` remain clean. `name` ≤ 20 chars (K13) is the only hard constraint.\n\n**Renderer note — formfield checkbox `[RENDERER-BUG]`.** LibreOffice's PDF export occasionally renders the formfield checkbox as `☐☐` (doubled box). Word and WPS render a single clickable box (toggles ☑). This is a LibreOffice renderer quirk, **not a skill or document quality issue** — see K19. Do not attempt workarounds in the form; if an evaluator screenshots a LibreOffice-generated PDF and sees `☐☐`, attribute to `[RENDERER-BUG]`.\n\n## Document protection & lock\n\n### Enabling form protection\n\n```bash\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli get \"$FILE\" /                                  # look for: protectionEnforced=True\n```\n\n### Protection modes\n\n| Mode | Word user can | CLI behavior |\n|---|---|---|\n| `forms` | Fill SDT + formfield only | Form-field (SDT) edits work (exit 0); non-field content edits need `--force` or `raw-set` |\n| `readOnly` | Read only | Non-field edits need `--force`; raw-set bypasses |\n| `comments` | Add comments only | Non-field edits need `--force`; raw-set bypasses |\n| `trackedChanges` | Edit with tracked changes only | Non-field edits need `--force`; raw-set bypasses |\n| `none` | Full editing | All ops work |\n\n**KEY:** Document protection restricts Word users AND the CLI. Under `protection=forms`, form-field (SDT) edits — `set /body/sdt[N] --prop text=` / `--prop alias=` — succeed (exit 0); this is the point of forms protection. Only NON-field content edits (e.g. `set /body/p[N] --prop text=`) are refused with `ERROR: Document is protected (mode: forms). … use --force to override`; pass `--force` (or `raw-set`, the only verb that fully bypasses protection) to edit static content anyway. Use `Query(\"editable\")` to find the fields a Word user could still fill.\n\n### Lock values (settable on `add` and `set`)\n\n```bash\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked           # content editable; control cannot be deleted\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=contentlocked       # content read-only; control can be deleted\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtcontentlocked    # both locked\n# Omit lock entirely → unlocked (default)\n```\n\n`--prop lock=...` works on `add` as well as `set` — set it inline when you create the control. Readback normalises to camelCase (`sdtLocked`) regardless of input case — both accepted.\n\n### lock × `protection=forms` interaction\n\n| lock value | `protection=forms` active | Word user can edit? | Word user can delete control? |\n|---|---|---|---|\n| (none) | yes | **Yes** | **Yes** |\n| `sdtlocked` | yes | Yes | No |\n| `contentlocked` | yes | No | Yes |\n| `sdtcontentlocked` | yes | No | No |\n| block-level SDT wrap `contentlocked` | any | No (wrapped paragraph read-only regardless of protection) | No |\n| any | `readOnly` mode | No | No |\n\n### Block-level lock (paragraph-wrapping SDT)\n\n`protection=forms` is document-level — once an admin unprotects, every static paragraph (disclaimer, legal attestation, contract clause) becomes editable again. Master templates need defense-in-depth: wrap the critical paragraph in a block-level `<w:sdt>` with `lock=contentLocked`, so the content stays read-only even after protection is stripped.\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"I authorize the above and acknowledge all clauses.\" --prop size=11 --prop spaceAfter=12pt\nPID=$(officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId')\n\n# raw-set actions: append | prepend | insertbefore | insertafter | replace | remove | setattr\n# No `wrap` action — two-step instead: (1) insertbefore an empty <w:sdt><w:sdtContent/></w:sdt>,\n# (2) move the original <w:p> inside by `replace` on the sdtContent with a copy of the paragraph XML.\n# Simpler alternative: read the paragraph XML via `officecli raw`, then `replace` the whole <w:p> with <w:sdt>...<w:sdtContent>[original w:p]</w:sdtContent></w:sdt>:\n# NOTE: this needs an XML-aware extraction, NOT awk. `raw /document` emits the whole document on ONE line,\n# so an awk range like `/paraId=.../,/<\\/w:p>/` grabs the entire <w:document> (schema-invalid). Parse the XML\n# and pull the single <w:p> by paraId instead:\nPARA_XML=$(officecli raw \"$FILE\" /document | python3 -c '\nimport sys, xml.etree.ElementTree as ET\nxml = sys.stdin.read(); pid = sys.argv[1]\nns = {\"w\":\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\"w14\":\"http://schemas.microsoft.com/office/word/2010/wordml\"}\nfor p, u in ns.items(): ET.register_namespace(p, u)\nroot = ET.fromstring(xml); key = \"{%s}paraId\" % ns[\"w14\"]\nfor p in root.iter(\"{%s}p\" % ns[\"w\"]):\n    if p.attrib.get(key) == pid:\n        sys.stdout.write(ET.tostring(p, encoding=\"unicode\")); break\n' \"$PID\")\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:p[@w14:paraId='$PID']\" \\\n  --action replace \\\n  --xml \"<w:sdt xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" xmlns:w14=\\\"http://schemas.microsoft.com/office/word/2010/wordml\\\"><w:sdtPr><w:alias w:val=\\\"Authorization\\\"/><w:tag w:val=\\\"auth_para\\\"/><w:lock w:val=\\\"contentLocked\\\"/></w:sdtPr><w:sdtContent>${PARA_XML}</w:sdtContent></w:sdt>\"\n```\n\nVerify with `query sdt --json | jq '.data.results[] | select(.format.lock == \"contentLocked\" and .format.type == \"block\")'`. Use only for legal attestations, compliance disclaimers, confidentiality clauses — regular intake fields do not need this.\n\n### Role-gated fields (multi-role forms)\n\nWhen one form is filled by two roles (patient vs physician; Party A vs Party B), use `lock=contentLocked` on the fields the other role must not touch. Under `protection=forms`, `contentLocked` SDTs display as read-only in Word; the intended role unprotects (or the admin swaps role-specific copies) to fill the other half.\n\n```bash\n# Patient section — editable (no lock, or sdtlocked to prevent accidental deletion only)\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked      # patient_name\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked      # patient_dob\n\n# Physician section — locked against patient edits\nofficecli set \"$FILE\" '/body/sdt[14]' --prop lock=contentLocked # physician_diagnosis\nofficecli set \"$FILE\" '/body/sdt[15]' --prop lock=contentLocked # physician_signature\n```\n\nThis is the core pattern for medical intake, two-party contracts, sequential-approval forms.\n\n## Recipe — Contract / SOW template with MERGEFIELD + signature\n\nRow-map across the three sub-recipes: SDT[1]=project_name, SDT[2]=contract_start, SDT[3]=payment_schedule, SDT[4]=signatory_name (inline). Run (sow-a) → (sow-b) → (sow-c) in order on the same `$FILE`; each sub-recipe stays under 20 lines so a shell-escape slip never cascades past one block.\n\n### Recipe (sow-a) Boilerplate + cover + parties\n\nCreates the file, sets docDefaults, writes the title / intro, and drops the two MERGEFIELD placeholders (`CustomerName`, `ContractNo`) that downstream mail-merge will fill.\n\n```bash\nFILE=sow.docx\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" / --prop title=\"Statement of Work\" \\\n  --prop docDefaults.font=\"Calibri\" --prop docDefaults.fontSize=\"12pt\"\n\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Statement of Work\" \\\n  --prop style=Heading1 --prop size=20 --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"This Statement of Work ('SOW') is entered into between the parties identified below and governs the delivery of professional services.\" \\\n  --prop size=11 --prop spaceAfter=12pt\n\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Customer: \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field \\\n  --prop fieldType=mergefield --prop name=CustomerName\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Contract #: \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field \\\n  --prop fieldType=mergefield --prop name=ContractNo\n```\n\n### Recipe (sow-b) SDT fields — all via Path A\n\nAdds the three block-level SDTs (project / date / dropdown) and the inline signature SDT anchored via `--after 'find:Client Signature:'`. The date format and dropdown items go straight on `add` — no raw-set needed.\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Project Name\" --prop tag=project_name --prop text=\"Enter project name\"\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Contract Start Date\" --prop tag=contract_start --prop format=\"MM/dd/yyyy\"\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown \\\n  --prop alias=\"Payment Schedule\" --prop tag=payment_schedule \\\n  --prop items=\"Full Prepayment,Net 30 Upon Delivery\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Client Signature:\" \\\n  --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Signatory Name\" --prop tag=signatory_name --prop text=\"Authorized Signatory\" \\\n  --after 'find:Client Signature:'\n# (Only reach for raw-set if a listItem's stored value must differ from its display text — see Path B.)\n```\n\n### Recipe (sow-c) Watermark + locks + document protection\n\nDrops the CONFIDENTIAL watermark (parent is `/`, never `/body`), locks the three block-level SDTs, instructs how to lock the inline signatory_name SDT (path only known after `view forms`), then seals the document with `protection=forms` as the last command.\n\n```bash\nofficecli add \"$FILE\" / --type watermark \\\n  --prop text=\"CONFIDENTIAL\" --prop color=FF0000 --prop rotation=315\n\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[3]' --prop lock=sdtlocked\nofficecli view \"$FILE\" forms   # copy signatory_name path, then: set '/body/p[@paraId=...]/sdt[1]' --prop lock=sdtlocked\n\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\nofficecli query \"$FILE\" field     # expect 2 MERGEFIELDs: CustomerName, ContractNo\n```\n\n## Design principles (forms)\n\n**Control-type decision tree:**\n\n```\nDate → type=date | Fixed list → type=dropdown | List + custom → type=combobox\nShort text → type=text | Long text → type=richtext | Boolean → formfield checkbox\n```\n\n**Typography scale.** Spacing unit trap: `spaceBefore` / `spaceAfter` / `spaceLine` default to **twips** (1/20 pt) — always write `spaceBefore=18pt`.\n\n| Element | Size | Style | Spacing |\n|---|---|---|---|\n| Form title (H1) | 20pt | Bold | `spaceBefore=0pt`, `spaceAfter=12pt` |\n| Section heading (H2) | 14pt | Bold | `spaceBefore=18pt`, `spaceAfter=8pt` |\n| Field label | 11pt | Bold | `spaceAfter=4pt` |\n| Instructions / notes | 11pt | Italic `color=666666` | `spaceAfter=18pt` |\n\n**Accessibility bump.** For medical / geriatric / accessibility-focused forms, raise field label + instruction to **12pt** (11pt default is tight for older users); keep section headings at 14pt.\n\n**CJK forms:** set `docDefaults.font=\"Microsoft YaHei\"` — Calibri lacks Chinese glyphs.\n\n**Field ordering.** (1) Personal / ID, (2) role / classification, (3) dates, (4) supplemental free-text, (5) confirmation / signature.\n\n**Yes/No + conditional follow-up** (common in compliance / medical intake): formfield checkbox followed by a richtext SDT whose `alias` carries the cue — e.g. `--type formfield --prop type=checkbox --prop name=has_cond` then `--type sdt --prop type=richtext --prop alias=\"If yes, explain\" --prop tag=cond_detail --prop text=\"If yes, explain here\"`.\n\n**Signature block order.** Label on its own paragraph, SDT on the next paragraph (with `spaceBefore=18pt` on the label, `spaceAfter=4pt` on the SDT). Never `Label: SDT` inline — Word renders the runs as touching, visually stuck together.\n\n**Build order.** create+open → metadata → structure (headings, label paragraphs) → SDT/formfield skeletons (Path A 4 props) → Path B injections → per-field lock → `protection=forms` LAST → close.\n\n**Header / footer note.** Headers/footers are **predefined** when the section is created (default/first/even, 3 each). The first mutation must be `set` against the existing part, not `add` — `add $FILE /header ...` returns `already exists` or silently no-ops. Inspect first with `officecli query \"$FILE\" header --json` to read the `type` values, then `officecli set \"$FILE\" '/header[@type=default]' --prop text=...`. Only use `add` when creating an additional section with its own header/footer.\n\n## Batch mode (brief)\n\nFor forms with many controls, batch reduces overhead. Path A + Path B coexist in one batch.\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/body\",\"type\":\"sdt\",\"props\":{\"type\":\"text\",\"alias\":\"Full Name\",\"tag\":\"full_name\",\"text\":\"Enter name\"}},\n  {\"command\":\"add\",\"parent\":\"/body\",\"type\":\"sdt\",\"props\":{\"type\":\"dropdown\",\"alias\":\"Department\",\"tag\":\"dept\"}},\n  {\"command\":\"raw-set\",\"part\":\"/document\",\"xpath\":\"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\",\"action\":\"append\",\"xml\":\"<w:listItem xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" w:displayText=\\\"Engineering\\\" w:value=\\\"Engineering\\\"/><w:listItem xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" w:displayText=\\\"Finance\\\" w:value=\\\"Finance\\\"/>\"},\n  {\"command\":\"set\",\"path\":\"/body/sdt[1]\",\"props\":{\"lock\":\"sdtlocked\"}},\n  {\"command\":\"set\",\"path\":\"/body/sdt[2]\",\"props\":{\"lock\":\"sdtlocked\"}}\n]\nEOF\nofficecli set \"$FILE\" / --prop protection=forms\n```\n\n- Escape inner `\"` in `xml` with `\\\"`. Use single-quoted heredoc `<<'EOF'` so `$var` does not expand.\n- **P0 batch trap:** genuinely-unsupported props in batch are silently dropped, **no WARNING** (interactive `add` would print WARNING: UNSUPPORTED, exit 2). Defence: send only props `help docx sdt` lists (`type/tag/alias/text/items/format/lock/...` are fine on `add`; `maxlength` is not), and verify with a readback after the batch.\n- `batch` supports `add`, `set`, `get`, `query`, `remove`, `validate`, `raw-set`.\n\n## Delivery Gate (executable)\n\nRun every gate below after every form. Each gate must print its `OK` line. Any `REJECT` = do not deliver.\n\n```bash\n# Assumes FILE=<your-form.docx>, document has been closed with officecli close \"$FILE\"\n\n# Gate 1 — Validate (must be clean; protection=forms no longer produces a schema error)\nVAL_OUT=$(officecli validate \"$FILE\" 2>&1)\nVAL_ERRS=$(echo \"$VAL_OUT\" | grep -c '\\[Schema\\]')\nif [ \"$VAL_ERRS\" -eq 0 ]; then echo \"Gate 1 OK (validate clean)\"\nelse echo \"REJECT Gate 1: $VAL_ERRS schema errors\"; echo \"$VAL_OUT\"; exit 1\nfi\n\n# Gate 2 — Token / placeholder leak (labels used as visual underscore substitutes)\nLEAK=$(officecli view \"$FILE\" text | grep -niE '_{3,}|TBD|\\(fill in\\)|\\{\\{|xxxx|lorem|placeholder')\n[ -z \"$LEAK\" ] && echo \"Gate 2 OK (no underscore / placeholder leak)\" || { echo \"REJECT Gate 2:\"; echo \"$LEAK\"; exit 1; }\n\n# Gate 3 — At least one structured field exists\nSDT_N=$(officecli query \"$FILE\" sdt --json | jq '.data.results | length')\nFF_N=$(officecli query \"$FILE\" formfield --json | jq '.data.results | length')\nFLD_N=$(officecli query \"$FILE\" field --json | jq '.data.results | length')\nTOTAL=$((SDT_N + FF_N + FLD_N))\n[ \"$TOTAL\" -gt 0 ] && echo \"Gate 3 OK ($SDT_N sdt + $FF_N formfield + $FLD_N field)\" || { echo \"REJECT Gate 3: 0 structured fields — this is not a form\"; exit 1; }\n\n# Gate 4 — Every SDT has alias + tag (skill-imposed H2)\n# NOTE: `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` — use `.data.results[0].format.alias` / `.format.tag`, never bare `.alias` or `.data.format`.\nSDT_MISSING=$(officecli query \"$FILE\" sdt --json | jq '[.data.results[] | select(.format.alias == null or .format.alias == \"\" or .format.tag == null or .format.tag == \"\")] | length')\n[ \"$SDT_MISSING\" -eq 0 ] && echo \"Gate 4 OK (every SDT has alias+tag)\" || { echo \"REJECT Gate 4: $SDT_MISSING SDT(s) missing alias or tag\"; exit 1; }\n\n# Gate 5 — Protection enforced + per-field lock inventory\nPROT=$(officecli get \"$FILE\" / --json | jq -r '.data.results[0].format.protection // \"none\"')\n[ \"$PROT\" = \"forms\" ] && echo \"Gate 5 OK (protection=forms enforced)\" || { echo \"REJECT Gate 5: protection is '$PROT', expected 'forms'\"; exit 1; }\nofficecli view \"$FILE\" forms | head -40   # visual spot-check: every dropdown shows items=; every date shows format=; every locked SDT shows lock=\n\n# Gate 6 — No type=checkbox leaked onto any SDT\nBAD_CB=$(officecli query \"$FILE\" sdt --json | jq '[.data.results[] | select(.format.type == \"checkbox\")] | length')\n[ \"$BAD_CB\" -eq 0 ] && echo \"Gate 6 OK (no SDT checkbox — formfield only)\" || { echo \"REJECT Gate 6: $BAD_CB SDT with type=checkbox\"; exit 1; }\n```\n\n**Why `view issues` is not a gate.** It runs only prose-style checks (first-line-indent, heading size) and flags every form label as `Body paragraph missing first-line indent` — a false-positive avalanche on forms. Ignore for this skill. Use `validate` (schema integrity) and `view forms` (field inventory).\n\n## Known Issues\n\n| # | Issue | Behavior | Workaround |\n|---|---|---|---|\n| K1 | SDT `type=checkbox` not implemented | `add ... --type sdt --prop type=checkbox` → `Error: SDT type 'checkbox' is not implemented`, exit 1 (error now lists the supported types incl. group/picture) | Use `--type formfield --prop type=checkbox` |\n| K3 | SDT `maxlength` UNSUPPORTED on `add` | `WARNING: UNSUPPORTED: maxlength`, exit 2; element still created. (`items` / `format` / `lock` / `placeholderText` are NOW supported on `add` — only `maxlength` is rejected; `name` is accepted but a no-op on SDT — use `alias`/`tag`) | Enforce length downstream; use `text` for initial content |\n| K4 | SDT `items` / `format` / `type` not settable AFTER creation | `set --prop items=...` → `UNSUPPORTED props (use raw-set instead)`. (They ARE settable on `add` — set them inline at creation) | Set at `add` time; to change later, Path B `raw-set` or `remove` + re-add |\n| K5 | FormField `maxlength` UNSUPPORTED | `WARNING: UNSUPPORTED: maxlength`; formfield created | Enforce length in downstream validation |\n| K6 | FormField dropdown `items` UNSUPPORTED | Dropdown formfield is created with empty option list | Use an SDT dropdown with `--prop items=` instead |\n| K7 | Watermark `width` / `height` not settable | Watermark created without them; `opacity` IS settable on `add` and reads back (real dims are computed) | For an exact size, open Word + adjust the shape (Phase 2) |\n| K9 | Batch mode silently drops genuinely-UNSUPPORTED props | No `WARNING` line; batch reports \"N succeeded\" even when a prop (e.g. `maxlength`) was dropped | Keep batch SDT entries to props `help docx sdt` lists; verify with a readback after the batch |\n| K13 | FormField `name` > 20 characters | `add` returns exit 0 with no warning; `validate` later reports `[Schema] ... MaxLength=20` on `/w:ffData/w:name` | Keep `name` ≤ 20 characters (OpenXML schema limit). SDT `alias` / `tag` have no such limit |\n| K14 | `shd.fill` on a paragraph emits schema-invalid `<w:pPr>/<w:shd>` | `validate` reports 2 schema errors per instance (`unexpected child element`, `required attribute 'val' missing`); Word renders it anyway | Apply highlight on the run instead (`shading=HEX`, flat canonical), or raw-set `<w:shd w:val=\"clear\" w:fill=\"HEX\"/>` into the run's `<w:rPr>` |\n| K15 | `view forms` does NOT list MERGEFIELDs | Only SDT + formfield in output; MERGEFIELDs are template-time, not end-user fillable | Treat `query field` and `view forms` as two disjoint inventories. Every recipe verifies both |\n| K16 | Header / footer are predefined at section creation (default/first/even, 3 each) | `add $FILE /header ...` returns `already exists` or silently no-ops on the first call | First mutation uses `set` against the existing part: `officecli query $FILE header --json` to read `type`, then `set '/header[@type=default]' --prop text=...`. Only use `add` for a brand-new section's header/footer |\n| K17 | Watermark injected into header emits `<w:noProof>` child that is schema-invalid | `validate` adds an extra `[Schema]` error at `/header[N]/w:sdt/.../w:noProof` — a real schema error to fix, not a benign one | After `add $FILE / --type watermark`, run once per header part: `officecli raw-set $FILE /word/header1.xml --xpath \"//w:noProof\" --action remove` (repeat for `header2.xml`, `header3.xml` if present) |\n| K18 | `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` | Writing jq against bare `.alias` / `.tag` / `.protection`, or `.data.format.*`, returns null/0 matches and Gate 4/5 falsely report \"missing=N\" | Use `.data.results[].format.alias` / `.format.tag`; for `get /` use `.data.results[0].format.protection` (NOT `.data.format.protection`). Same for `.format.type` / `.format.paraId` |\n| K19 | LibreOffice renders formfield checkbox as `☐☐` (double box) in PDF export | Cosmetic only — Word / WPS render a single box, clickable to toggle ☑. A LibreOffice renderer quirk, flagged as [RENDERER-BUG] | Do not try to \"fix\" in the skill. If an evaluator screenshots from LibreOffice-generated PDF and sees `☐☐`, attribute to [RENDERER-BUG], not a form-quality defect |\n\n## Phase 2 — enhance in Word\n\nSome polish is out of CLI scope. Hand the file to a human for these; none are required for a valid form.\n\n| Need | Why open Word |\n|---|---|\n| Real SDT checkbox with specific locking | `type=checkbox` exits 1; use Developer → Check Box Content Control (or a legacy FormField checkbox via CLI) |\n| Prompt text (\"Click here to enter a date\") | Needs `placeholderDocPart` in `/word/glossary/document.xml` |\n| Custom richtext default appearance | Adjust the referenced style in Word's style pane |\n| Watermark resize | `width` / `height` not settable; drag shape handles |\n\n(`picture` and `group` SDTs add directly via `--type sdt --prop type=picture` / `type=group` — no longer Phase-2 items.)\n\nFor the first four, build the skeleton once (Path C) and reuse.\n\n## Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <element> --json`. Help is the authoritative schema; this skill is the decision guide for building real fillable Word forms on top of it.\n","SKILL.md":"---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n","skills/officecli/SKILL.md":"---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n","plugins/plugin-protocol.md":"# Plugins Skill\n\nPath: `plugins/plugin-protocol.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/plugins/plugin-protocol.md)","skills/morph-ppt/reference/decision-rules.md":"# Reference Skill\n\nPath: `skills/morph-ppt/reference/decision-rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/decision-rules.md)","skills/morph-ppt/reference/pptx-design.md":"# Reference Skill\n\nPath: `skills/morph-ppt/reference/pptx-design.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/pptx-design.md)","skills/morph-ppt/reference/styles/INDEX.md":"# Styles Skill\n\nPath: `skills/morph-ppt/reference/styles/INDEX.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/INDEX.md)","skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md":"# Bw--brutalist-raw Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md)","skills/morph-ppt/reference/styles/bw--mono-line/style.md":"# Bw--mono-line Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--mono-line/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--mono-line/style.md)","skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md":"# Bw--swiss-bauhaus Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md)","skills/morph-ppt/reference/styles/bw--swiss-system/style.md":"# Bw--swiss-system Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-system/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-system/style.md)","skills/morph-ppt/reference/styles/dark--architectural-plan/style.md":"# Dark--architectural-plan Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--architectural-plan/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--architectural-plan/style.md)","skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md":"# Dark--aurora-softedge Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md)","skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md":"# Dark--blueprint-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md)","skills/morph-ppt/reference/styles/dark--circle-digital/style.md":"# Dark--circle-digital Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--circle-digital/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--circle-digital/style.md)","skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md":"# Dark--cosmic-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md)","skills/morph-ppt/reference/styles/dark--cyber-future/style.md":"# Dark--cyber-future Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cyber-future/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cyber-future/style.md)","skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md":"# Dark--diagonal-cut Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md)","skills/morph-ppt/reference/styles/dark--editorial-story/style.md":"# Dark--editorial-story Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--editorial-story/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--editorial-story/style.md)","skills/morph-ppt/reference/styles/dark--investor-pitch/style.md":"# Dark--investor-pitch Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--investor-pitch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--investor-pitch/style.md)","skills/morph-ppt/reference/styles/dark--liquid-flow/style.md":"# Dark--liquid-flow Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--liquid-flow/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--liquid-flow/style.md)","skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md":"# Dark--luxury-minimal Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md)","skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md":"# Dark--midnight-blueprint Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md)","skills/morph-ppt/reference/styles/dark--neon-productivity/style.md":"# Dark--neon-productivity Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--neon-productivity/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--neon-productivity/style.md)","skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md":"# Dark--obsidian-amber Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md)","skills/morph-ppt/reference/styles/dark--premium-navy/style.md":"# Dark--premium-navy Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--premium-navy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--premium-navy/style.md)","skills/morph-ppt/reference/styles/dark--sage-grain/style.md":"# Dark--sage-grain Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--sage-grain/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--sage-grain/style.md)","skills/morph-ppt/reference/styles/dark--space-odyssey/style.md":"# Dark--space-odyssey Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--space-odyssey/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--space-odyssey/style.md)","skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md":"# Dark--spotlight-stage Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md)","skills/morph-ppt/reference/styles/dark--velvet-rose/style.md":"# Dark--velvet-rose Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--velvet-rose/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--velvet-rose/style.md)","skills/morph-ppt/reference/styles/light--bold-type/style.md":"# Light--bold-type Skill\n\nPath: `skills/morph-ppt/reference/styles/light--bold-type/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--bold-type/style.md)","skills/morph-ppt/reference/styles/light--firmwise-saas/style.md":"# Light--firmwise-saas Skill\n\nPath: `skills/morph-ppt/reference/styles/light--firmwise-saas/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--firmwise-saas/style.md)","skills/morph-ppt/reference/styles/light--fluid-gradient/style.md":"# Light--fluid-gradient Skill\n\nPath: `skills/morph-ppt/reference/styles/light--fluid-gradient/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--fluid-gradient/style.md)","skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md":"# Light--glassmorphism-vc Skill\n\nPath: `skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md)","skills/morph-ppt/reference/styles/light--isometric-clean/style.md":"# Light--isometric-clean Skill\n\nPath: `skills/morph-ppt/reference/styles/light--isometric-clean/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--isometric-clean/style.md)","skills/morph-ppt/reference/styles/light--minimal-corporate/style.md":"# Light--minimal-corporate Skill\n\nPath: `skills/morph-ppt/reference/styles/light--minimal-corporate/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-corporate/style.md)","skills/morph-ppt/reference/styles/light--minimal-product/style.md":"# Light--minimal-product Skill\n\nPath: `skills/morph-ppt/reference/styles/light--minimal-product/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-product/style.md)","skills/morph-ppt/reference/styles/light--project-proposal/style.md":"# Light--project-proposal Skill\n\nPath: `skills/morph-ppt/reference/styles/light--project-proposal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--project-proposal/style.md)","skills/morph-ppt/reference/styles/light--spring-launch/style.md":"# Light--spring-launch Skill\n\nPath: `skills/morph-ppt/reference/styles/light--spring-launch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--spring-launch/style.md)","skills/morph-ppt/reference/styles/light--training-interactive/style.md":"# Light--training-interactive Skill\n\nPath: `skills/morph-ppt/reference/styles/light--training-interactive/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--training-interactive/style.md)","skills/morph-ppt/reference/styles/light--watercolor-wash/style.md":"# Light--watercolor-wash Skill\n\nPath: `skills/morph-ppt/reference/styles/light--watercolor-wash/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--watercolor-wash/style.md)","skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md":"# Mixed--bauhaus-blocks Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md)","skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md":"# Mixed--chromatic-aberration Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md)","skills/morph-ppt/reference/styles/mixed--duotone-split/style.md":"# Mixed--duotone-split Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--duotone-split/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--duotone-split/style.md)","skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md":"# Mixed--spectral-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md)","skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md":"# Vivid--bauhaus-electric Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md)","skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md":"# Vivid--candy-stripe Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md)","skills/morph-ppt/reference/styles/vivid--energy-neon/style.md":"# Vivid--energy-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--energy-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--energy-neon/style.md)","skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md":"# Vivid--pink-editorial Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md)","skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md":"# Vivid--playful-marketing Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md)","skills/morph-ppt/reference/styles/warm--bloom-academy/style.md":"# Warm--bloom-academy Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--bloom-academy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--bloom-academy/style.md)","skills/morph-ppt/reference/styles/warm--brand-refresh/style.md":"# Warm--brand-refresh Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--brand-refresh/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--brand-refresh/style.md)","skills/morph-ppt/reference/styles/warm--coral-culture/style.md":"# Warm--coral-culture Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--coral-culture/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--coral-culture/style.md)","skills/morph-ppt/reference/styles/warm--earth-organic/style.md":"# Warm--earth-organic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--earth-organic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--earth-organic/style.md)","skills/morph-ppt/reference/styles/warm--monument-editorial/style.md":"# Warm--monument-editorial Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--monument-editorial/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--monument-editorial/style.md)","skills/morph-ppt/reference/styles/warm--playful-organic/style.md":"# Warm--playful-organic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--playful-organic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--playful-organic/style.md)","skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md":"# Warm--sunset-mosaic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md)","skills/morph-ppt/reference/styles/warm--vital-bloom/style.md":"# Warm--vital-bloom Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--vital-bloom/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--vital-bloom/style.md)"},"items":[{"name":"plugin-protocol.md","path":"plugins/plugin-protocol.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/plugins/plugin-protocol.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `plugins/plugin-protocol.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/plugins/plugin-protocol.md)","tokens":300,"sizeBytes":168},{"name":"SKILL.md","path":"SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/SKILL.md","title":"officecli","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n","frontmatter":{"name":"officecli","description":"Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents."},"isInternal":false,"tokens":6957,"sizeBytes":25974},{"name":"SKILL.md","path":"skills/morph-ppt-3d/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt-3d/SKILL.md","title":"morph-ppt-3d","category":"anthropic-skill","format":"markdown","content":"---\nname: morph-ppt-3d\ndescription: 3D Morph PPT — extends morph-ppt with GLB model insertion, cinematographic camera, model-content layout, and enriched visual design system.\n---\n\n# Morph PPT — 3D Extension\n\nThis skill **extends** `morph-ppt`. All morph-ppt rules (naming, ghosting, design, verification) apply in full.\nThis file covers **3D-specific additions** and an **enriched design system** combining morph-ppt aesthetics with concrete color palettes, font pairings, and layout quality guardrails.\n\n---\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Use when\n\n- User wants a `.pptx` with a `.glb` 3D model and Morph transitions.\n\n---\n\n## 3D Model Compatibility Gate (before generation)\n\n1. Only `.glb` is supported. If user provides `.fbx` / `.obj` / `.blend` / `.usdz` / `.gltf`, ask them to convert to `.glb` first (e.g. via Blender export).\n2. If user has no model, follow the **Model Discovery Flow** below.\n3. All files (`.glb`, `.pptx`, build script) must be in the same working directory.\n\n---\n\n## Model Discovery Flow (when user has no model)\n\nWhen the user gives a topic but no `.glb` file, **proactively help them find a matching model** instead of just listing websites.\n\n### Step 1: Understand the topic and suggest model direction\n\nBased on the user's topic, suggest what kind of 3D model would work:\n\n| Topic type         | Model suggestion                    | Example                                               |\n| ------------------ | ----------------------------------- | ----------------------------------------------------- |\n| Product/brand      | The actual product or a similar one | \"coffee brand\" → coffee cup, coffee machine, bean     |\n| Animal/character   | The animal or mascot                | \"fox mascot\" → fox 3D model                           |\n| Architecture/space | Building, room, or structure        | \"new office\" → office building, interior              |\n| Vehicle/transport  | The vehicle itself                  | \"EV launch\" → car, motorcycle, bicycle                |\n| Food/cooking       | The dish or ingredient              | \"Japanese food\" → sushi platter, ramen bowl           |\n| Tech/gadget        | The device                          | \"phone launch\" → phone, tablet, laptop                |\n| Nature/science     | The subject                         | \"solar system\" → planet, sun, earth                   |\n| Abstract concept   | A symbolic object                   | \"teamwork\" → puzzle pieces, gears, bridge             |\n\nTell the user: \"Your topic is [X]. I suggest using a 3D model of [description]. Here are some free sources to find one:\"\n\n### Step 2: Search for models (agent-driven)\n\n**Proactively search for models on behalf of the user.** Don't just list websites — actually find candidates.\n\n**Search strategy (try in order):**\n\n1. **Web search** for free GLB models matching the topic:\n\n   ```\n   Search: \"[topic keyword] 3d model glb free download\"\n   Example: \"fox 3d model glb free download\"\n   ```\n\n2. **Sketchfab API** (no auth needed for search):\n\n   ```bash\n   curl -s \"https://api.sketchfab.com/v3/search?type=models&q=[keyword]&downloadable=true&archives_flavours=glb\" \\\n     | python3 -c \"\n   import json, sys\n   data = json.load(sys.stdin)\n   for m in data.get('results', [])[:5]:\n       print(f\\\"Name: {m['name']}\\\")\n       print(f\\\"URL: https://sketchfab.com/3d-models/{m['slug']}-{m['uid']}\\\")\n       print(f\\\"Likes: {m.get('likeCount', 0)}, License: {m.get('license', {}).get('label', 'unknown')}\\\")\n       print()\n   \"\n   ```\n\n3. **Poly Pizza** (direct GLB download, all free):\n\n   ```bash\n   # Search results page — parse for download links\n   curl -s \"https://poly.pizza/api/search/[keyword]\" 2>/dev/null\n   ```\n\n4. **Khronos glTF-Sample-Assets** (guaranteed to work, always available):\n   ```bash\n   # Direct download — no auth, no API, always works\n   curl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/[ModelName]/glTF-Binary/[ModelName].glb\"\n   ```\n   Available models: Duck, Fox, Avocado, BrainStem, CesiumMan, DamagedHelmet, FlightHelmet, Lantern, Suzanne, WaterBottle, etc.\n\n### Step 3: Present candidates to user for confirmation\n\nShow the user 2-3 model options with:\n\n- Model name and source\n- Preview link (Sketchfab URL or description)\n- License info\n- Why this model fits their topic\n\nExample response:\n\n```\nBased on your topic \"fox mascot\", here are some models I found:\n\n1. Fox (Khronos sample)\n   Direct download, guaranteed compatible\n   Why: clean fox model, good for mascot/character decks\n\n2. Low Poly Fox (Poly Pizza)\n   URL: https://poly.pizza/m/xxx\n   License: CC0 (completely free)\n   Why: low-poly style, good fit for clean minimal design\n\n3. Cartoon Fox (Sketchfab)\n   URL: https://sketchfab.com/3d-models/fox-xxx\n   License: CC BY 4.0 (free, commercial use ok)\n   Why: expressive face, high detail\n\nWhich one do you want? I'll download it and start building.\n```\n\n**Wait for user confirmation before downloading.** Do not download without asking.\n\n### Step 4: Download the confirmed model\n\nAfter user confirms, download directly:\n\n```bash\n# For Sketchfab (if user has the download URL)\ncurl -L -o model.glb \"[download_url]\"\n\n# For Khronos samples (always works)\ncurl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Fox/glTF-Binary/Fox.glb\"\n```\n\nAfter download, verify:\n\n- File exists and is not empty\n- File extension is `.glb`\n- File size is under 50MB\n\nIf Sketchfab requires login to download, tell the user:\n\n> \"This model requires a Sketchfab login to download. You can grab the .glb file from the page and share it with me. Or I can use a Khronos sample model for a demo version first?\"\n\n### Step 5: When user says \"anything\" / \"you decide\" / \"just make a demo\"\n\n**Don't just grab a random model.** First guide the user to clarify their PPT topic:\n\n> Sure! I'll handle the model — but let me confirm the topic direction first so the model matches the content:\n>\n> 1. Tech/Product — headphones, phone, robot...\n> 2. Animal/Character — cute pet, cartoon character...\n> 3. Architecture/Space — building, interior, city...\n> 4. Food/Lifestyle — dishes, everyday objects...\n> 5. Other — just tell me your idea\n>\n> Pick a direction, or just give me a topic keyword.\n\nAfter user confirms a direction, THEN search and recommend models.\n\n### Step 6: When user wants to find models themselves\n\nGive specific website links with step-by-step guidance:\n\n> **Recommended 3D model websites:**\n>\n> 1. **Sketchfab** (largest 3D model platform)\n>    - Link: https://sketchfab.com/search?q=[keyword]&type=models&downloadable=true\n>    - Filter steps: search keyword → check \"Downloadable\" → format \"glTF\" → sort by \"Likes\"\n>    - When downloading, select **glTF (.glb)** format\n>    - Note: some models require free registration to download\n> 2. **Poly Pizza** (all free low-poly)\n>    - Link: https://poly.pizza/\n>    - All CC0 licensed — click Download to get .glb directly\n>    - Best for: minimalist or cartoon-style presentations\n> 3. **Sketchfab popular categories**\n>    - Animals: https://sketchfab.com/search?q=animal&type=models&downloadable=true\n>    - Food: https://sketchfab.com/search?q=food&type=models&downloadable=true\n>    - Tech: https://sketchfab.com/search?q=gadget&type=models&downloadable=true\n>    - Architecture: https://sketchfab.com/search?q=architecture&type=models&downloadable=true\n> 4. **Free3D** (general free model site)\n>    - Link: https://free3d.com/3d-models/glb\n>    - Note: check the license type before use\n> 5. **TurboSquid Free** (pro model site free section)\n>    - Link: https://www.turbosquid.com/Search/3D-Models/free/glb\n>\n> After downloading, share the .glb file with me. If the download is a .gltf folder, use Blender to convert it to .glb.\n\n### Step 7: When user gives keywords and asks agent to search\n\n**Remind about token cost before searching:**\n\n> I can search for you, but web searches use extra tokens. Would you prefer:\n>\n> A. I search — I use the Sketchfab API and recommend 2-3 options (uses a few tokens)\n> B. Self-service — I give you search links and filter steps, you pick and share with me (no extra tokens)\n>\n> A or B?\n\nIf user chooses A, proceed with Step 2 (agent-driven search).\nIf user chooses B, proceed with Step 6 (self-service guidance).\n\n### License reminder\n\nAlways remind before confirming download: \"Please check the model license before downloading. CC0 / CC BY = free to use; CC BY-NC = non-commercial only.\"\n\n---\n\n## Visual Design System (4.0 enrichment)\n\nmorph-ppt provides the base design rules. This section adds **concrete palettes, font pairings, and layout quality rules** from PPT Creator to give the AI more variety and stronger guardrails.\n\n### Color Palettes (pick one per deck, or blend)\n\nChoose a palette that matches the **topic mood** — don't default to generic blue.\n\n| Palette                | Primary               | Secondary             | Accent           | Body Text | Muted/Caption |\n| ---------------------- | --------------------- | --------------------- | ---------------- | --------- | ------------- |\n| **Coral Energy**       | `F96167` (coral)      | `F9E795` (gold)       | `2F3C7E` (navy)  | `333333`  | `8B7E6A`      |\n| **Midnight Executive** | `1E2761` (navy)       | `CADCFC` (ice blue)   | `FFFFFF`         | `333333`  | `8899BB`      |\n| **Forest & Moss**      | `2C5F2D` (forest)     | `97BC62` (moss)       | `F5F5F5` (cream) | `2D2D2D`  | `6B8E6B`      |\n| **Charcoal Minimal**   | `36454F` (charcoal)   | `F2F2F2` (off-white)  | `212121`         | `333333`  | `7A8A94`      |\n| **Warm Terracotta**    | `B85042` (terracotta) | `E7E8D1` (sand)       | `A7BEAE` (sage)  | `3D2B2B`  | `8C7B75`      |\n| **Berry & Cream**      | `6D2E46` (berry)      | `A26769` (dusty rose) | `ECE2D0` (cream) | `3D2233`  | `8C6B7A`      |\n| **Ocean Gradient**     | `065A82` (deep blue)  | `1C7293` (teal)       | `21295C`         | `2B3A4E`  | `6B8FAA`      |\n| **Teal Trust**         | `028090` (teal)       | `00A896` (seafoam)    | `02C39A` (mint)  | `2D3B3B`  | `5E8C8C`      |\n| **Sage Calm**          | `84B59F` (sage)       | `69A297` (eucalyptus) | `50808E`         | `2D3D35`  | `7A9488`      |\n| **Cherry Bold**        | `990011` (cherry)     | `FCF6F5` (off-white)  | `2F3C7E` (navy)  | `333333`  | `8B6B6B`      |\n\n**Rules:**\n\n- One color dominates (60-70% visual weight), 1-2 supporting tones, one accent\n- On light backgrounds: use Body Text color for copy, Muted for captions\n- On dark backgrounds: use Secondary or `FFFFFF` for copy, Muted for captions\n- For additional inspiration, browse `../morph-ppt/reference/styles/INDEX.md` — 50+ visual styles organized by mood (dark, light, warm, vivid, bw). Read `style.md` for design philosophy, `build.sh` for implementation reference. **Learn the approach, do not copy coordinates verbatim**\n\n### Font Pairings (pick one per deck)\n\n| Header Font  | Body Font     | Best For                         |\n| ------------ | ------------- | -------------------------------- |\n| Georgia      | Calibri       | Formal business, finance         |\n| Arial Black  | Arial         | Bold marketing, product launches |\n| Calibri      | Calibri Light | Clean corporate, minimal         |\n| Cambria      | Calibri       | Traditional professional         |\n| Trebuchet MS | Calibri       | Friendly tech, startups          |\n| Impact       | Arial         | Bold headlines, keynotes         |\n| Palatino     | Garamond      | Elegant editorial, luxury        |\n| Consolas     | Calibri       | Developer tools, technical       |\n\n### Hard Rules (mandatory, no exceptions)\n\n**H4 — Body text minimum 16pt:**\nAll body text, card content, and bullet points must be >= 16pt. \"Content doesn't fit\" is not an excuse — reduce text, split slides, or reduce card count instead. Exceptions: chart axis labels (<=12pt), short sublabels (<=14pt, max 5 words), footnotes.\n\n**H6 — Dark background contrast:**\nWhen slide background brightness < 30% (e.g. `1E2761`, `36454F`, `000000`), ALL body text, card content, chart labels, and icon fills MUST use white (`FFFFFF`) or near-white (brightness > 80%). Never use mid-gray or muted colors as body text on dark backgrounds.\n\n**H7 — Speaker notes required:**\nEvery content slide (not title/closing) MUST have speaker notes. Use:\n\n```bash\nofficecli add deck.pptx '/slide[N]' --type notes --prop text=\"...\"\n```\n\n### Visual Element Checkpoint\n\n**Every 3 content slides, at least 1 must contain a non-text visual element:**\n\n| Visual type            | Implementation                               |\n| ---------------------- | -------------------------------------------- |\n| Icon in colored circle | ellipse shape + centered text/number overlay |\n| Colored block          | `preset=roundRect` with fill                 |\n| Large stat number      | `size=64, bold=true` with small label below  |\n| Chart                  | `--type chart` (column/pie/line)             |\n| Gradient background    | `background=COLOR1-COLOR2-180`               |\n| Shape composition      | circles + connectors for diagrams            |\n\nText-only slides are only allowed for: quotes, code examples, pure tables.\n\n---\n\n## 3D Model Insertion Rules\n\n### Add model fresh on every slide — NEVER clone\n\n`morph_clone_slide` copies the model as frozen XML. The cloned model cannot Morph.\nEach slide must call `add --type 3dmodel` independently with the **same `name`** prop.\n\n**⚠️ CRITICAL: If you clone a slide that already has a 3D model, the old model XML is copied too. This creates TWO model3d elements with the same name on the new slide. PowerPoint cannot handle this conflict and will delete the model content during repair.**\n\nIf you must clone a slide for scene actors, **immediately remove the cloned model before adding a new one:**\n\n```bash\n# After cloning slide 1 to slide 2:\nofficecli remove deck.pptx '/slide[2]/model3d[1]'  # remove the frozen clone\nofficecli add deck.pptx '/slide[2]' --type 3dmodel ...  # add fresh model\n```\n\n**Recommended approach: Do NOT clone slides with 3D models at all.** Create all slides empty first, then add models fresh on each.\n\n```bash\n# Slide 1\nofficecli add deck.pptx '/slide[1]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=16cm --prop y=1cm --prop width=16cm --prop height=16cm \\\n  --prop roty=0\n\n# Slide 2\nofficecli add deck.pptx '/slide[2]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=0.5cm --prop y=1cm --prop width=18cm --prop height=17cm \\\n  --prop roty=50\n```\n\n### Controllable properties\n\n| Property          | What it does              | Notes                                         |\n| ----------------- | ------------------------- | --------------------------------------------- |\n| `x`, `y`          | Position on slide         | Standard slide coordinates                    |\n| `width`, `height` | Frame size                | Model renders inside this frame               |\n| `name`            | Shape name                | Must be identical across slides for Morph     |\n| `roty`            | Y-axis rotation (degrees) | Primary storytelling axis                     |\n| `rotx`            | X-axis tilt (degrees)     | Range -25 to +40. See Camera Language section |\n| `rotz`            | Z-axis roll (degrees)     | Rarely needed                                 |\n\n### Do NOT manually set\n\n- `meterPerModelUnit` — auto-computed from GLB bounding box\n- `preTrans` — auto-computed for model centering\n- `camera` depth/position — auto-computed to fit the model\n- Never use `raw-set` on any 3D transform parameter\n\n---\n\n## Model-Content Layout\n\n### Core Principle: Model IS the Subject\n\nThe model must feel like the **protagonist** of the presentation, not a sidebar decoration.\nText supports the model; the model does not decorate the text.\n\n### Size Contrast Rule (MANDATORY)\n\nAdjacent slides must have a model area ratio >= 1.5x or <= 0.67x.\nCompute area as `width × height`. If slide N model is 16×15=240 cm², slide N+1 must be >= 360 or <= 160.\n\n**Never use similar sizes on consecutive slides.** This is the single most important rule for visual energy.\n\n| Size tier      | Width   | Height  | Area (approx) | When to use                                |\n| -------------- | ------- | ------- | ------------- | ------------------------------------------ |\n| **XL (bleed)** | 28-36cm | 22-28cm | 600-1000      | Close-up, model extends beyond slide edges |\n| **L (hero)**   | 18-24cm | 15-19cm | 270-456       | Title, closing, dramatic moments           |\n| **M (split)**  | 13-17cm | 12-16cm | 156-272       | Standard content pages with text           |\n| **S (accent)** | 5-10cm  | 5-10cm  | 25-100        | Data-heavy pages, model as icon            |\n\n### Layout Patterns (6 types)\n\n**A — Model right, content left** (content pages)\nContent at x=1-14cm. Model at x=15-20cm, width 14-18cm.\n\n**B — Model left, content right** (alternate with A)\nModel at x=0-2cm, width 14-18cm. Content at x=18-32cm.\n\n**C — Model centered, text overlay** (title/closing)\nModel centered large (18-24cm). Text at slide top or bottom.\n\n**D — Model small corner, content dominant** (data pages)\nModel 5-10cm in any corner. Content fills the rest.\n\n**E — Model as backdrop** (impact/quote pages)\nModel XL (28-36cm), centered, partially cropped by slide edges.\nText overlaid directly on top of model area with high-contrast color.\nThe model becomes the \"canvas\" — text lives inside the model's space.\n\n```bash\n# Pattern E: model fills slide as backdrop\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=-2cm --prop y=-2cm --prop width=38cm --prop height=24cm \\\n  --prop roty=45 --prop rotx=10\n\n# Text overlaid on model\nofficecli add deck.pptx '/slide[N]' --type shape \\\n  --prop 'name=#sN-quote' --prop text=\"Key insight here\" \\\n  --prop x=3cm --prop y=7cm --prop width=28cm --prop height=5cm \\\n  --prop size=44 --prop bold=true --prop color=FFFFFF --prop fill=none\n```\n\n**F — Model bleed edge** (transition/teaser pages)\nModel partially off-screen (negative x or y, or x+width > 33.87cm).\nOnly part of the model visible — implies more beyond the frame.\n\n```bash\n# Pattern F: model bleeds off right edge\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=20cm --prop y=-1cm --prop width=24cm --prop height=22cm \\\n  --prop roty=70\n```\n\n### Layout Progression\n\nNever repeat the same pattern on consecutive slides. Example:\n\n```\nSlide 1: C (centered hero, L)\nSlide 2: E (backdrop close-up, XL)   ← 1.5x+ area jump\nSlide 3: A (model right, M)          ← pull back\nSlide 4: F (bleed edge, L)           ← push in\nSlide 5: D (small corner, S)         ← dramatic pull back\nSlide 6: B (model left, M)           ← grow\nSlide 7: C (centered closing, L)     ← push in\n```\n\n### Text Layout Safety (MANDATORY)\n\n**Text boxes must never overlap each other or the model frame.**\n\nRules:\n\n1. **Title and body must not collide.** If a title wraps to 2 lines, the body `y` must account for the title's actual height, not the planned height. Safe formula: `body_y = title_y + title_height + 0.5cm`\n2. **Fixed-height text boxes are dangerous.** If text content is longer than expected, it will overflow invisibly. Use generous heights: title `3-4cm`, body `6-8cm`, bullets `8-10cm`.\n3. **Model frame and text boxes: gap >= 1cm.** Calculate: if model is at `x=15cm`, text `x + width` must be <= `14cm`.\n4. **On Pattern C (centered model + text overlay):** text goes at slide top (`y=0.5-2cm`) or bottom (`y=14-17cm`), NOT in the vertical middle where the model lives (`y=3-13cm`).\n5. **After building each slide, verify coordinates:**\n   ```bash\n   officecli get deck.pptx '/slide[N]' --depth 1\n   # Check: no two shapes share overlapping x/y/width/height ranges\n   ```\n\n### Model Bleed Guidelines\n\n**Not every model looks good when cropped.** Bleed (Pattern E/F) works best for:\n\n- ✅ Symmetric objects (spheres, helmets, bottles) — any crop looks intentional\n- ✅ Large flat surfaces (cars, buildings) — partial view implies scale\n- ✅ When cropping non-critical parts (background, base, stand)\n\nBleed does NOT work for:\n\n- ❌ Character/animal models — cropping ears, tails, or limbs looks broken\n- ❌ Small detailed models — cropping loses the detail you want to show\n- ❌ When the cropped part is the most recognizable feature\n\n**For character/animal models (like fox, duck, avocado):** keep the full model visible on all slides. Use size changes (L→M→S) for rhythm instead of bleed cropping. Use `rotx` for angle variety instead.\n\n---\n\n## Camera Language\n\nThree tools work together: **roty** (orbit), **rotx** (tilt), **width/height** (zoom).\n\n### Shot Types (use >= 3 different per deck)\n\n| Shot                     | Size                  | rotx       | When                        |\n| ------------------------ | --------------------- | ---------- | --------------------------- |\n| **Establishing**         | L (18-24cm)           | 0-5        | Title, intro, closing       |\n| **Three-quarter beauty** | L (16-20cm)           | 5-10       | Hero, first impression      |\n| **Close-up**             | XL (28-36cm), cropped | 0-10       | Feature highlight, detail   |\n| **Bird's eye**           | M (13-17cm)           | 25-40      | Structure, overview         |\n| **Low angle**            | L (16-20cm)           | -15 to -25 | Power, drama                |\n| **Side profile**         | M (13-16cm)           | 0          | Form factor, silhouette     |\n| **Over-the-shoulder**    | S (5-10cm)            | 10-15      | Data-heavy, model as accent |\n\n### Content-Driven Camera\n\nMatch the shot to what the slide talks about:\n\n- \"Front design\" → Close-up, `roty=0`, XL cropped\n- \"Side profile\" → Side, `roty=90`, M\n- \"Internal structure\" → Bird's eye, `roty=30, rotx=35`, M\n- \"Power/authority\" → Low angle, `roty=20, rotx=-20`, L\n- \"Data & specs\" → Over-the-shoulder, `roty=60`, S in corner\n\n### Rotation Rules\n\n1. Adjacent roty delta: 30-90° (< 30 = jitter, > 90 = disorienting)\n2. Overall roty direction must be consistent (no back-and-forth)\n3. rotx range: -25 to +40. Adjacent rotx delta <= 20\n4. Total arc across deck: 180-360° (show the model from all sides)\n\n### Example Shot Plan\n\n| Slide | Shot                 | roty | rotx | Size     | Pattern |\n| ----- | -------------------- | ---- | ---- | -------- | ------- |\n| 1     | Three-quarter beauty | 30   | 8    | L 20×17  | C       |\n| 2     | Close-up             | 0    | 5    | XL 30×24 | E       |\n| 3     | Side profile         | 80   | 0    | M 15×14  | A       |\n| 4     | Bird's eye           | 120  | 35   | M 14×13  | B       |\n| 5     | Low angle            | 170  | -20  | L 20×18  | F       |\n| 6     | Over-the-shoulder    | 220  | 10   | S 8×7    | D       |\n| 7     | Establishing         | 320  | 5    | L 20×17  | C       |\n\n---\n\n## Workflow Integration with morph-ppt\n\n### Phase 2 additions (Planning)\n\nIn `brief.md`, add a **Model Choreography Table**:\n\n| Slide | Pattern | Size Tier | Model x,y,w,h | roty | rotx |\n| ----- | ------- | --------- | ------------- | ---- | ---- |\n| 1     | C       | L         | 7,0.5,20,17   | 30   | 8    |\n| 2     | E       | XL        | -2,-2,38,24   | 0    | 5    |\n| ...   | ...     | ...       | ...           | ...  | ...  |\n\nVerify the area ratio rule (>= 1.5x between adjacent rows) before proceeding to build.\n\n### Phase 3 additions (Build)\n\nSince models cannot be cloned, the build script differs from standard morph-ppt:\n\n1. Create all slides first (with background + morph transition)\n2. Add scene actors (`!!scene-*`) on slide 1, then clone slides for morph continuity\n3. Add 3D model fresh on EACH slide (same name, different roty/position)\n4. Add content shapes per slide, ghost previous content\n\n```python\nmodel_positions = [\n    {\"slide\": 1, \"x\": \"7cm\",  \"y\": \"0.5cm\", \"w\": \"20cm\", \"h\": \"17cm\", \"roty\": 30},\n    {\"slide\": 2, \"x\": \"-2cm\", \"y\": \"-2cm\",  \"w\": \"38cm\", \"h\": \"24cm\", \"roty\": 0},\n    {\"slide\": 3, \"x\": \"16cm\", \"y\": \"1cm\",   \"w\": \"15cm\", \"h\": \"14cm\", \"roty\": 80},\n    # ...\n]\nfor pos in model_positions:\n    run(\"officecli\", \"add\", OUTPUT, f\"/slide[{pos['slide']}]\", \"--type\", \"3dmodel\",\n        \"--prop\", f\"path={MODEL}\", \"--prop\", \"name=!!model-hero\",\n        \"--prop\", f\"x={pos['x']}\", \"--prop\", f\"y={pos['y']}\",\n        \"--prop\", f\"width={pos['w']}\", \"--prop\", f\"height={pos['h']}\",\n        \"--prop\", f\"roty={pos['roty']}\")\n```\n\n### Phase 4 additions (Verification)\n\nAfter standard morph verification, additionally check:\n\n- Each slide has exactly one `model3d` element\n- All models share the same `name` prop\n- Adjacent slides have model area ratio >= 1.5x or <= 0.67x\n- No two consecutive slides use the same layout pattern\n\n---\n\n## File Placement Rule\n\nAll files must be in the same working directory.\n\n**Deliverables (exactly 4 files, no more):**\n\n- `.glb` model file (the 3D model used in the deck)\n- Output `.pptx`\n- Build script (re-runnable)\n- `brief.md`\n\n**Do NOT create additional files** such as outline.md, quality-report.md, test-report.md, etc. All planning goes in `brief.md`, all verification output goes to stdout. Extra files confuse users.\n\nDo not scatter model files across unrelated paths.\n","frontmatter":{"name":"morph-ppt-3d","description":"3D Morph PPT — extends morph-ppt with GLB model insertion, cinematographic camera, model-content layout, and enriched visual design system."},"isInternal":false,"tokens":7082,"sizeBytes":25711},{"name":"decision-rules.md","path":"skills/morph-ppt/reference/decision-rules.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/decision-rules.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"# Reference Skill\n\nPath: `skills/morph-ppt/reference/decision-rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/decision-rules.md)","tokens":300,"sizeBytes":206},{"name":"pptx-design.md","path":"skills/morph-ppt/reference/pptx-design.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/pptx-design.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"# Reference Skill\n\nPath: `skills/morph-ppt/reference/pptx-design.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/pptx-design.md)","tokens":300,"sizeBytes":200},{"name":"style.md","path":"skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md","title":"Bw--brutalist-raw Skill","category":"anthropic-skill","format":"markdown","content":"# Bw--brutalist-raw Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md)","tokens":300,"sizeBytes":246},{"name":"style.md","path":"skills/morph-ppt/reference/styles/bw--mono-line/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--mono-line/style.md","title":"Bw--mono-line Skill","category":"anthropic-skill","format":"markdown","content":"# Bw--mono-line Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--mono-line/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--mono-line/style.md)","tokens":300,"sizeBytes":234},{"name":"style.md","path":"skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md","title":"Bw--swiss-bauhaus Skill","category":"anthropic-skill","format":"markdown","content":"# Bw--swiss-bauhaus Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md)","tokens":300,"sizeBytes":246},{"name":"style.md","path":"skills/morph-ppt/reference/styles/bw--swiss-system/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-system/style.md","title":"Bw--swiss-system Skill","category":"anthropic-skill","format":"markdown","content":"# Bw--swiss-system Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-system/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-system/style.md)","tokens":300,"sizeBytes":243},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--architectural-plan/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--architectural-plan/style.md","title":"Dark--architectural-plan Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--architectural-plan Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--architectural-plan/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--architectural-plan/style.md)","tokens":300,"sizeBytes":267},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md","title":"Dark--aurora-softedge Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--aurora-softedge Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md","title":"Dark--blueprint-grid Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--blueprint-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--circle-digital/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--circle-digital/style.md","title":"Dark--circle-digital Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--circle-digital Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--circle-digital/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--circle-digital/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md","title":"Dark--cosmic-neon Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--cosmic-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md)","tokens":300,"sizeBytes":246},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--cyber-future/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cyber-future/style.md","title":"Dark--cyber-future Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--cyber-future Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cyber-future/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cyber-future/style.md)","tokens":300,"sizeBytes":249},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md","title":"Dark--diagonal-cut Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--diagonal-cut Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md)","tokens":300,"sizeBytes":249},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--editorial-story/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--editorial-story/style.md","title":"Dark--editorial-story Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--editorial-story Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--editorial-story/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--editorial-story/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--investor-pitch/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--investor-pitch/style.md","title":"Dark--investor-pitch Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--investor-pitch Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--investor-pitch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--investor-pitch/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--liquid-flow/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--liquid-flow/style.md","title":"Dark--liquid-flow Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--liquid-flow Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--liquid-flow/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--liquid-flow/style.md)","tokens":300,"sizeBytes":246},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md","title":"Dark--luxury-minimal Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--luxury-minimal Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md","title":"Dark--midnight-blueprint Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--midnight-blueprint Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md)","tokens":300,"sizeBytes":267},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--neon-productivity/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--neon-productivity/style.md","title":"Dark--neon-productivity Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--neon-productivity Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--neon-productivity/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--neon-productivity/style.md)","tokens":300,"sizeBytes":264},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md","title":"Dark--obsidian-amber Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--obsidian-amber Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--premium-navy/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--premium-navy/style.md","title":"Dark--premium-navy Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--premium-navy Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--premium-navy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--premium-navy/style.md)","tokens":300,"sizeBytes":249},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--sage-grain/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--sage-grain/style.md","title":"Dark--sage-grain Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--sage-grain Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--sage-grain/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--sage-grain/style.md)","tokens":300,"sizeBytes":243},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--space-odyssey/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--space-odyssey/style.md","title":"Dark--space-odyssey Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--space-odyssey Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--space-odyssey/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--space-odyssey/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md","title":"Dark--spotlight-stage Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--spotlight-stage Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/dark--velvet-rose/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--velvet-rose/style.md","title":"Dark--velvet-rose Skill","category":"anthropic-skill","format":"markdown","content":"# Dark--velvet-rose Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--velvet-rose/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--velvet-rose/style.md)","tokens":300,"sizeBytes":246},{"name":"INDEX.md","path":"skills/morph-ppt/reference/styles/INDEX.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/INDEX.md","title":"Styles Skill","category":"anthropic-skill","format":"markdown","content":"# Styles Skill\n\nPath: `skills/morph-ppt/reference/styles/INDEX.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/INDEX.md)","tokens":300,"sizeBytes":199},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--bold-type/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--bold-type/style.md","title":"Light--bold-type Skill","category":"anthropic-skill","format":"markdown","content":"# Light--bold-type Skill\n\nPath: `skills/morph-ppt/reference/styles/light--bold-type/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--bold-type/style.md)","tokens":300,"sizeBytes":243},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--firmwise-saas/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--firmwise-saas/style.md","title":"Light--firmwise-saas Skill","category":"anthropic-skill","format":"markdown","content":"# Light--firmwise-saas Skill\n\nPath: `skills/morph-ppt/reference/styles/light--firmwise-saas/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--firmwise-saas/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--fluid-gradient/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--fluid-gradient/style.md","title":"Light--fluid-gradient Skill","category":"anthropic-skill","format":"markdown","content":"# Light--fluid-gradient Skill\n\nPath: `skills/morph-ppt/reference/styles/light--fluid-gradient/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--fluid-gradient/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md","title":"Light--glassmorphism-vc Skill","category":"anthropic-skill","format":"markdown","content":"# Light--glassmorphism-vc Skill\n\nPath: `skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md)","tokens":300,"sizeBytes":264},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--isometric-clean/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--isometric-clean/style.md","title":"Light--isometric-clean Skill","category":"anthropic-skill","format":"markdown","content":"# Light--isometric-clean Skill\n\nPath: `skills/morph-ppt/reference/styles/light--isometric-clean/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--isometric-clean/style.md)","tokens":300,"sizeBytes":261},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--minimal-corporate/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-corporate/style.md","title":"Light--minimal-corporate Skill","category":"anthropic-skill","format":"markdown","content":"# Light--minimal-corporate Skill\n\nPath: `skills/morph-ppt/reference/styles/light--minimal-corporate/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-corporate/style.md)","tokens":300,"sizeBytes":267},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--minimal-product/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-product/style.md","title":"Light--minimal-product Skill","category":"anthropic-skill","format":"markdown","content":"# Light--minimal-product Skill\n\nPath: `skills/morph-ppt/reference/styles/light--minimal-product/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--minimal-product/style.md)","tokens":300,"sizeBytes":261},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--project-proposal/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--project-proposal/style.md","title":"Light--project-proposal Skill","category":"anthropic-skill","format":"markdown","content":"# Light--project-proposal Skill\n\nPath: `skills/morph-ppt/reference/styles/light--project-proposal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--project-proposal/style.md)","tokens":300,"sizeBytes":264},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--spring-launch/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--spring-launch/style.md","title":"Light--spring-launch Skill","category":"anthropic-skill","format":"markdown","content":"# Light--spring-launch Skill\n\nPath: `skills/morph-ppt/reference/styles/light--spring-launch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--spring-launch/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--training-interactive/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--training-interactive/style.md","title":"Light--training-interactive Skill","category":"anthropic-skill","format":"markdown","content":"# Light--training-interactive Skill\n\nPath: `skills/morph-ppt/reference/styles/light--training-interactive/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--training-interactive/style.md)","tokens":300,"sizeBytes":276},{"name":"style.md","path":"skills/morph-ppt/reference/styles/light--watercolor-wash/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--watercolor-wash/style.md","title":"Light--watercolor-wash Skill","category":"anthropic-skill","format":"markdown","content":"# Light--watercolor-wash Skill\n\nPath: `skills/morph-ppt/reference/styles/light--watercolor-wash/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--watercolor-wash/style.md)","tokens":300,"sizeBytes":261},{"name":"style.md","path":"skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md","title":"Mixed--bauhaus-blocks Skill","category":"anthropic-skill","format":"markdown","content":"# Mixed--bauhaus-blocks Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md","title":"Mixed--chromatic-aberration Skill","category":"anthropic-skill","format":"markdown","content":"# Mixed--chromatic-aberration Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md)","tokens":300,"sizeBytes":276},{"name":"style.md","path":"skills/morph-ppt/reference/styles/mixed--duotone-split/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--duotone-split/style.md","title":"Mixed--duotone-split Skill","category":"anthropic-skill","format":"markdown","content":"# Mixed--duotone-split Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--duotone-split/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--duotone-split/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md","title":"Mixed--spectral-grid Skill","category":"anthropic-skill","format":"markdown","content":"# Mixed--spectral-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md)","tokens":300,"sizeBytes":255},{"name":"style.md","path":"skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md","title":"Vivid--bauhaus-electric Skill","category":"anthropic-skill","format":"markdown","content":"# Vivid--bauhaus-electric Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md)","tokens":300,"sizeBytes":264},{"name":"style.md","path":"skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md","title":"Vivid--candy-stripe Skill","category":"anthropic-skill","format":"markdown","content":"# Vivid--candy-stripe Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/vivid--energy-neon/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--energy-neon/style.md","title":"Vivid--energy-neon Skill","category":"anthropic-skill","format":"markdown","content":"# Vivid--energy-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--energy-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--energy-neon/style.md)","tokens":300,"sizeBytes":249},{"name":"style.md","path":"skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md","title":"Vivid--pink-editorial Skill","category":"anthropic-skill","format":"markdown","content":"# Vivid--pink-editorial Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md","title":"Vivid--playful-marketing Skill","category":"anthropic-skill","format":"markdown","content":"# Vivid--playful-marketing Skill\n\nPath: `skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md)","tokens":300,"sizeBytes":267},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--bloom-academy/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--bloom-academy/style.md","title":"Warm--bloom-academy Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--bloom-academy Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--bloom-academy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--bloom-academy/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--brand-refresh/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--brand-refresh/style.md","title":"Warm--brand-refresh Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--brand-refresh Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--brand-refresh/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--brand-refresh/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--coral-culture/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--coral-culture/style.md","title":"Warm--coral-culture Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--coral-culture Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--coral-culture/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--coral-culture/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--earth-organic/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--earth-organic/style.md","title":"Warm--earth-organic Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--earth-organic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--earth-organic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--earth-organic/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--monument-editorial/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--monument-editorial/style.md","title":"Warm--monument-editorial Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--monument-editorial Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--monument-editorial/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--monument-editorial/style.md)","tokens":300,"sizeBytes":267},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--playful-organic/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--playful-organic/style.md","title":"Warm--playful-organic Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--playful-organic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--playful-organic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--playful-organic/style.md)","tokens":300,"sizeBytes":258},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md","title":"Warm--sunset-mosaic Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--sunset-mosaic Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md)","tokens":300,"sizeBytes":252},{"name":"style.md","path":"skills/morph-ppt/reference/styles/warm--vital-bloom/style.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--vital-bloom/style.md","title":"Warm--vital-bloom Skill","category":"anthropic-skill","format":"markdown","content":"# Warm--vital-bloom Skill\n\nPath: `skills/morph-ppt/reference/styles/warm--vital-bloom/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/warm--vital-bloom/style.md)","tokens":300,"sizeBytes":246},{"name":"SKILL.md","path":"skills/morph-ppt/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/SKILL.md","title":"morph-ppt","category":"anthropic-skill","format":"markdown","content":"---\nname: morph-ppt\ndescription: \"Use this skill when the user wants a .pptx with smooth cross-slide animation — PowerPoint Morph transitions, Keynote-style continuous motion, shapes that grow / move / rotate as the slide advances. Trigger on: 'morph', 'morph transition', 'smooth transition', 'continuous animation across slides', 'Keynote-style transition', 'animated slide sequence', 'shape continuity across slides'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate 1–5a). DO NOT invoke for a generic deck, pitch deck, or board review without cross-slide motion — route those to officecli-pptx base or officecli-pitch-deck.\"\n---\n\n# OfficeCLI Morph-PPT Skill\n\n**This skill is a scene layer on top of `officecli-pptx`.** Every pptx hard rule — visual delivery floor (title ≥ 36pt / body ≥ 18pt / title ≥ 2× body), 12-column grid on 33.87×19.05cm, canonical palettes, chart-choice decision table, connector canon, shell escape, resident + batch, Delivery Gate 1–5a — is inherited, not re-taught. This file adds only what **Morph** needs on top: cross-slide shape-name binding, Scene Actors vs content prefixing, ghost discipline, `transition=morph` CLI quirks, 52-style visual library lookup, and a morph-specific fresh-eyes Gate 5b extension.\n\nWhen the pptx base rules cover it, the text here says `→ see pptx v2 §X`. Read `skills/officecli-pptx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches the Morph workflow — when shape names must match, when to ghost, when the CLI auto-prefixes — not every command flag.** When a prop name, enum, or preset is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help pptx slide           # authoritative for: transition, advanceTime, advanceClick, background\nofficecli help pptx transition      # transition / transitionDuration / transitionSpeed (Parent: slide)\nofficecli help pptx shape           # name, preset, x/y/width/height, fill, rotation, opacity, animation\nofficecli help pptx animation       # preset + trigger + duration values\nofficecli help pptx <element> --json  # machine-readable schema\n```\n\nHelp reflects the installed CLI version. When skill and help disagree, **help wins.** Every `--prop X=` in this file is grep-verified against `officecli help pptx <element>`. Specific confirmations: `transition=morph` is a listed value on `slide`; `advanceTime` / `advanceClick` are valid. `transition` is a real element (`officecli help pptx transition`, Parent: slide, set/get) — it exposes `transition`, `transitionDuration`, and `transitionSpeed`. Set the transition with the high-level path `set <slide> --prop transition=morph`; tune speed/duration with the combined shorthand `transition=morph-slow` (or `-fast`, or `transition=morph-<DUR_MS>`). Speed/duration are set only via that shorthand on the `transition` prop, not as independent sub-props. Both round-trip on readback: `transition=morph-slow`/`-fast` reads back as `transitionSpeed=slow`/`fast`, and `transition=morph-<DUR_MS>` (e.g. `morph-1500`) reads back as `transitionDuration=1500`.\n\n## Mental Model & Inheritance\n\n**Inherits pptx v2.** You should have read `skills/officecli-pptx/SKILL.md` first. This skill assumes you know how to: add slides + shapes + charts + connectors; address by `@name=` / `@id=`; quote paths; use `batch` heredocs; use `tailEnd=triangle` on flow connectors; run the Delivery Gate 1–5a; attribute `[AGENT-ERROR]` vs `[RENDERER-BUG]` vs `[SKILL gap]`. If any of those are unfamiliar, read pptx v2 first.\n\n**Inherited from pptx v2 (do NOT re-teach):**\n\n- Visual delivery floor — title ≥ 36pt / body ≥ 18pt / title ≥ 2× body, cover-richness, contrast floor, no `\\$\\t\\n` literals, ≤ 1 animation per slide / ≤ 600ms.\n- Grid math — 33.87 × 19.05cm, edge margin ≥ 1.27cm, inter-block gap ≥ 0.76cm, ≥ 20% negative space. For N-card grids: `col = (33.87 − 2·margin − (N−1)·gap) / N`.\n- Four canonical palettes (Executive navy / Forest & moss / Warm terracotta / Charcoal minimal) — morph decks may pick a different mood from `reference/styles/`, but contrast rules still apply.\n- Chart-choice table — column vs bar vs line vs pie vs scatter vs large-text KPI; `> 3 series + > 8 categories` = split.\n- Connector canon — `shape=straight|elbow|curve`, `@id=` for from/to (C-P-6), `tailEnd=triangle` on every flow.\n- Shell escape 3-layer — `$` single-quoted, heredocs for batch, `<a:br/>` for real newlines.\n- Resident mode + batch ≤ 12 ops, `<<'EOF'` single-quoted delimiter.\n- Delivery Gate 1-5a (schema, token grep, hyperlink rPr, slide-order, dark-on-dark) — every gate prints OK before declaring done.\n- Known Issues C-P-1..7 (hyperlink rPr, chart spPr warning, animation duration readback, animation remove, connector enum, connector `@name=`, chart color renderer normalization).\n- Attribution triage — `[AGENT-ERROR]` vs `[RENDERER-BUG]` vs `[SKILL gap]`.\n\n**Morph identity — what this skill owns (delta on top of pptx v2):**\n\n- **Cross-slide shape-name binding.** PowerPoint's Morph engine pairs shapes by **identical `name=`** across adjacent slides and interpolates their position / size / rotation / fill / opacity. No matching name ⇒ no animation, silent fade. This is a workflow discipline, not a CLI feature.\n- **Namespace prefixes:** `!!scene-*` (persistent decoration, never ghosted) / `!!actor-*` (content that evolves then exits) / `#sN-*` (per-slide content, ghosted on slide N+1). Plan the names BEFORE you `add`.\n- **Ghost position `x=36cm`** (off the right edge of the 33.87cm canvas). Never delete a `!!`-prefixed shape — move it off-canvas so the morph exit animation still plays.\n- **`transition=morph` auto-prefix quirk.** The CLI auto-prepends `!!` to every shape on a morph slide (`#s1-title` is stored as `!!#s1-title`). `@name=` path selectors **still resolve** — `get .../shape[@name=#s1-title]` returns the shape (matching is suffix/prefix-tolerant). The name you read back is the prefixed form. See §Known Issues.\n- **Adjacent-slide spatial variety.** Displacement ≥ 5cm or rotation ≥ 15° between pairs — otherwise morph interpolates nothing visible.\n- **Renderer reality.** Morph renders in PowerPoint 365 / Keynote / WPS. LibreOffice and many web viewers render as plain fade (runtime feature). Not a skill defect — `[RENDERER-BUG]`.\n\n### Reverse handoff — when to go BACK to pptx base (or sibling skills)\n\nStay in **pptx v2 base** for any deck without cross-slide motion (board reviews, sales decks, all-hands, training). Stay in **officecli-pitch-deck** for fundraising narrative arcs without morph. Use this skill only when the user explicitly asks for \"morph\" / \"smooth transitions\" / \"continuous animation\" AND ≥ 2 consecutive slides share a visual element that transforms. \"Animated deck\" meaning one-off entrance animations → pptx v2 §Animations, not morph.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see pptx v2 §Shell & Execution Discipline. Same rules verbatim.\n\n**Morph-specific additions:**\n\n- **`!!` in shell values — single-quote.** Bash / zsh history expansion eats unquoted `!!foo`. Always use `--prop 'name=!!scene-ring'` (single quotes). In Python `subprocess.run([...])` lists, no quoting needed — pass `\"name=!!scene-ring\"` as a plain string.\n- **`$` in prop text — single-quote (price tokens).** `--prop text='$9/mo'` and `--prop text='$199/yr'` — NEVER `--prop text=\"$9/mo\"` (zsh/bash eat `$9` as empty var → text rendered as `.` / stray period). Same for `${VAR}`, `$USER`, `\\n`, `\\r`, `\\t` inside a double-quoted prop. Gate 2 morph addendum below greps for the leak signature.\n- **`#` in shell values — safe, but quote anyway.** `#` is a comment leader only at the start of a shell word. `--prop name=#s1-title` works, but `--prop 'name=#s1-title'` is the habit that stops you guessing.\n- **Batch heredoc is the cleanest path for multi-shape slides.** `<<'EOF' | officecli batch $FILE` disables all shell expansion — safe for `$`, `!!`, `#`, `'` inside the JSON body.\n- **`--json` responses wrap the payload in `.data.results[]`.** Both `query` and `get` return a `.data.results[]` array. A single node's `format` sits at `.data.results[0].format.X`; that node's children sit at `.data.results[0].children[]` (each child's format at `.data.results[0].children[].format.X`). Always go through `.data.results[0]` — bare `.data.children[]` or `.data.format` returns null silently.\n- **Variable:** `FILE=\"deck.pptx\"` at the top of every build script; every example below uses `$FILE`.\n- **Gate shell pattern — COUNT, then if/else.** Never write `grep … && echo LEAK || echo OK` — when grep exits 1 (0 matches), the `||` branch fires with empty stdout and prints \"OK\" confusingly (or prints \"LEAK\" from prior pipes). Canonical form: `COUNT=$(cmd | wc -l); if [ \"$COUNT\" -gt 0 ]; then echo \"LEAK: …\"; else echo \"OK\"; fi`.\n\n## Two primitives this skill owns\n\n- **Scene Actors** = persistent `!!`-named shapes (decoration or content) **paired by identical name** across adjacent slides so Morph can interpolate them. Every `!!scene-*` / `!!actor-*` shape is a scene actor.\n- **Choreography** = the plan for how actors evolve — who moves where, who enters, who exits, on which slide pair. Written BEFORE code in the §Morph Pair Planning table.\n\nUse this skill when the user asks for morph motion AND ≥ 2 consecutive slides share a visual element that transforms. Target-viewer caveat: morph needs PowerPoint 365 / Keynote / WPS — if the user is LibreOffice-only, warn first (see §Renderer honesty).\n\n**Speaker notes rule.** Every content slide (non-cover, non-closing) MUST carry speaker notes via `officecli add \"$FILE\" /slide[N] --type notes --prop text='…'`. Missing notes = not shippable — inherits pptx v2 §Hard rules (H7). Morph decks tend to be visually minimal, so notes carry the narration.\n\n## What is Morph? (core mechanics)\n\nPowerPoint's Morph transition creates smooth motion by interpolating shape properties between adjacent slides, matched by **identical shape names**.\n\n```\nSlide 1: shape name=\"!!scene-ring\" x=5cm  width=8cm   fill=E94560 opacity=0.3\nSlide 2: shape name=\"!!scene-ring\" x=20cm width=12cm fill=E94560 opacity=0.6\n         ↓  transition=morph on slide 2\nResult:  Ring smoothly moves, grows, and fades darker over ~1 second\n```\n\nMorph only runs if slide N+1 carries `transition=morph`. Apply it via `officecli add / --type slide --prop transition=morph` on creation, or `officecli set \"/slide[N]\" --prop transition=morph` after the fact. Slides 2+ that omit this prop fall back to whatever the master defines (usually no transition) — motion dies silently.\n\n**Three-prefix naming system (non-negotiable):**\n\n| Prefix | Role | Lifecycle | Example |\n|---|---|---|---|\n| `!!scene-*` | Background / decoration — persists across the entire deck | Set once, adjust position/size to create motion; **rarely ghosted** | `!!scene-ring`, `!!scene-bg-band`, `!!scene-grid` |\n| `!!actor-*` | Content / foreground — evolves across a section | Introduced on slide N, modified on slide N+1, N+2…, **ghosted to `x=36cm`** on its exit slide | `!!actor-feature-box`, `!!actor-metric`, `!!actor-headline` |\n| `#sN-*` | Per-slide content (titles, bullets, captions) | Added fresh on slide N, **ghosted to `x=36cm`** on slide N+1 | `#s1-title`, `#s2-kpi`, `#s3-caption` |\n\n**Hard rule:** `!!scene-*` and `!!actor-*` names must NEVER collide (e.g., `!!scene-card` + `!!actor-card` in the same deck — morph engine confuses them). Disambiguate: `!!scene-card-bg` vs `!!actor-card-content`.\n\n**Charts can be morph-paired.** `officecli add … --type chart` accepts `--prop name=!!…` (the name reads back), so a chart with an identical `!!`-name on adjacent slides participates in shape-name morph pairing — the chart frame interpolates position / size. Note morph cannot interpolate the *plotted data* inside the chart frame. For bar-grow / line-grow narratives where the bars themselves must animate: (a) accept plain fade-in of the chart as-is, OR (b) build N `!!actor-bar-K` rectangles manually sized to the values and morph those — each rect carries the same `!!actor-bar-K` name across adjacent slides while width / height / fill evolves.\n\n**Ghost accumulation is silent.** Once a `!!`-prefixed shape appears on any slide, it stays visible on every subsequent morph slide unless explicitly moved to `x=36cm`. `final-check` helper does NOT detect `!!` shapes lingering in the visible area — **only Gate 5b screenshot audit does.** Plan every actor's exit slide in the pair table BEFORE coding.\n\n**Spatial variety rule.** Adjacent slides must have **noticeably different** compositions — displacement ≥ 5cm OR rotation ≥ 15° OR size delta ≥ 30% on at least 3 morph-paired shapes. Without this, morph interpolates nothing visible and the transition collapses to a fade (silent-fail).\n\n**Simultaneous-timing constraint.** All `!!` shapes in one morph pair animate simultaneously. To stagger shape A before shape B, insert an intermediate keyframe slide — there is no per-shape delay knob.\n\n**Paired vs enter vs exit — three behaviors, one rule.** Same mechanism (shape-name match) produces three outcomes:\n\n| Behavior | Source slide A | Target slide B | Who carries `!!`? |\n|---|---|---|---|\n| **Paired morph** (interpolate) | has `!!foo` | has `!!foo` | both slides, identical name |\n| **Enter** (fade / morph-in) | — (no counterpart) | has `!!foo` | target only — new shape |\n| **Exit via ghost** (slide off) | has `!!foo` at visible `x` | has `!!foo` at `x=36cm` | both — same name, B is off-canvas |\n\n**Outgoing content (not incoming) is what gets `!!`-prefixed + ghosted.** `!!actor-*` shapes silently \"disappear\" when you forget them — their name going missing on slide B reads as an unpaired exit (plain fade). Always explicit-ghost to `x=36cm` so the exit animation slides off the right edge visibly. One runnable example:\n\n```bash\n# Slide 2: actor is visible at x=5cm — Slide 3: same name, ghosted off-canvas → visible slide-off motion\nofficecli add \"$FILE\" \"/slide[3]\" --type shape --prop 'name=!!actor-metric' \\\n  --prop text=\"42%\" --prop x=36cm --prop y=8cm --prop width=6cm --prop height=3cm\n```\n\n**Content (`#sN-*`) is added fresh per slide.** Because text changes every slide, Morph has no meaningful pairing to do on titles / body — it cross-fades them. This is why `#sN-*` get different names per slide (they are intentionally unpaired) and must be ghosted on slide N+1. Scene actors (`!!`) carry the continuity; content (`#`) carries the message.\n\n## Morph Pair Planning (pre-code, REQUIRED)\n\nBefore planning morph pairs, if the deck's audience / purpose / narrative is underspecified, run the planning prompt in `reference/decision-rules.md` to emit a `brief.md` first — a morph arc without a narrative spine collapses into \"slide with motion\", not \"story with motion\".\n\nPlan every transition in a table inside `brief.md` **before** writing any `officecli add`. Renaming shapes mid-build is the #1 cause of ghost accumulation bugs.\n\n| Pair | Slide A (start) | Slide B (end) | Actors in play | Ghost on Slide B |\n|---|---|---|---|---|\n| 1→2 | `!!scene-ring` centered 5cm, `#s1-title` visible | Ring shifts to x=20cm, grows 8→12cm; `#s2-subtitle` revealed | `!!scene-ring` evolves | `#s1-title` → x=36cm |\n| 2→3 | `!!actor-feature-box` large (14cm wide) | Feature box small (6cm), `!!actor-metric` enters | `!!scene-ring`, `!!actor-feature-box`, `!!actor-metric` | `#s2-subtitle` → x=36cm |\n| 3→4 | Content section A | Section B divider | — | `!!actor-feature-box` + `!!actor-metric` → x=36cm (section-exit); `#s3-*` → x=36cm |\n\n**Planning rules:**\n\n1. Decide ALL `!!` names up front — each morph-paired shape must use the **exact same name** on both slides.\n2. Classify every `!!` shape as `!!scene-*` or `!!actor-*`. Scene shapes persist; actors must have a planned exit slide.\n3. **Section-transition boundary:** when moving into a new topic section, ghost ALL previous-section `!!actor-*` on the first slide of the new section. Only `!!scene-*` (whole-deck decoration) remains.\n4. Do NOT start building until the table is complete. If the plan changes mid-build, redraw the table and re-verify affected slides.\n\n## Morph Recipes (4 patterns)\n\nFour patterns cover ~95% of morph decks. `$FILE=\"deck.pptx\"` throughout. Each block is self-contained and ≤ 20 lines.\n\n### (a) Single-element morph — size / position\n\n**Visual outcome.** A hero title centered on slide 1 (size 48pt at y=8cm), then slide 2 shrinks it to 32pt and shifts it to the top-left corner (x=1.5cm, y=1cm) — letting fresh slide-2 content take center stage. One shape, clean motion, no actors.\n\n```bash\nFILE=\"deck.pptx\"\nofficecli create \"$FILE\"; officecli open \"$FILE\"\n\n# Slide 1 — hero\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!actor-headline' \\\n  --prop text=\"The one idea\" --prop x=4cm --prop y=8cm --prop width=26cm --prop height=3cm \\\n  --prop font=Georgia --prop size=48 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\n\n# Slide 2 — headline shrinks + moves; new body takes stage\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761 --prop transition=morph\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!actor-headline' \\\n  --prop text=\"The one idea\" --prop x=1.5cm --prop y=1cm --prop width=12cm --prop height=1.5cm \\\n  --prop font=Georgia --prop size=24 --prop bold=true --prop color=FFFFFF --prop align=left --prop fill=none\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=#s2-body' \\\n  --prop text=\"Here is the supporting evidence.\" --prop x=1.5cm --prop y=5cm --prop width=30cm --prop height=2cm \\\n  --prop font=Calibri --prop size=20 --prop color=CADCFC --prop fill=none\n\nofficecli close \"$FILE\"; officecli validate \"$FILE\"\n```\n\n### (b) Multi-element coordinated morph — Actors / Choreography\n\n**Visual outcome.** Three scene actors (`!!scene-ring`, `!!scene-dot`, `!!scene-band`) repositioned across 3 slides to feel like a camera pan. Fresh per-slide titles fade in / out via the `#sN-*` ghost pattern. Use this when the narrative has a continuous visual backdrop.\n\n```bash\n# Slide 1 — anchor composition (already built via recipe a; here we add actors)\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!scene-ring' --prop preset=ellipse \\\n  --prop fill=E94560 --prop opacity=0.3 --prop x=5cm --prop y=3cm --prop width=8cm --prop height=8cm\nofficecli add \"$FILE\" /slide[1] --type shape --prop 'name=!!scene-dot' --prop preset=ellipse \\\n  --prop fill=0F3460 --prop x=28cm --prop y=15cm --prop width=1cm --prop height=1cm\n\n# Slide 2 — morph: ring moves + grows, dot slides left (spatial variety ≥ 5cm on both)\nofficecli set \"$FILE\" \"/slide[2]\" --prop transition=morph\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!scene-ring' --prop preset=ellipse \\\n  --prop fill=E94560 --prop opacity=0.6 --prop x=20cm --prop y=2cm --prop width=12cm --prop height=12cm\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=!!scene-dot' --prop preset=ellipse \\\n  --prop fill=0F3460 --prop x=3cm --prop y=16cm --prop width=1.5cm --prop height=1.5cm\n# Ghost slide-1 content (name path still resolves after morph — see Known Issues)\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=#s1-title]\" --prop x=36cm 2>/dev/null || true\n\n# Verify morph pair: identical names on slides 1 & 2\nofficecli get \"$FILE\" /slide[1] --depth 1 --json | jq -r '.data.results[0].children[]?.format.name // empty'\nofficecli get \"$FILE\" /slide[2] --depth 1 --json | jq -r '.data.results[0].children[]?.format.name // empty'\n# Compare — `!!scene-ring` and `!!scene-dot` MUST appear on both, byte-identical.\n# Note: morph stores names with a `!!` prefix; compare the prefixed forms.\n```\n\n### (c) Continuous multi-slide morph (story arc) — use helpers\n\n**Visual outcome.** A 5-slide arc telling one continuous story: same 2 scene actors drift across the canvas as the narrative progresses; content (`#sN-*`) refreshes per slide and is ghosted on the next. Building this by hand is ~60 commands — use `reference/morph-helpers.py` to keep the build script short and auto-verified.\n\n```python\n#!/usr/bin/env python3\n# Invoke the provided helper library for clone + ghost + verify\nimport subprocess, sys, os\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\nHELPERS = os.path.join(SCRIPT_DIR, \"reference\", \"morph-helpers.py\")\nFILE = \"deck.pptx\"\n\ndef helper(*args):\n    subprocess.run([sys.executable, HELPERS, *[str(a) for a in args]], check=True)\n\n# ... assume slide 1 is built with 2 scene actors (!!scene-ring, !!scene-dot) + #s1-title\n# Helper builds slide 2–5 with: clone from previous + apply transition=morph + ghost previous #sN- content\n# `clone` prints the cloned slide's shape list — read it to pick which shape indices carry the\n# previous slide's #s(n-1)- content, then pass those explicit indices to `ghost`.\nfor n in range(2, 6):\n    helper(\"clone\", FILE, n - 1, n)          # clone + set transition=morph + list shapes (note the #s(n-1)- indices)\n    helper(\"ghost\", FILE, n, 1, 2)           # ghost the #s(n-1)- content shapes by index (here shapes 1 & 2)\n    # …then add THIS slide's #sN- content via officecli add as normal…\nhelper(\"final-check\", FILE)                   # structural pass; DOES NOT catch !! lingering in visible area\n```\n\nHelper signatures and source: `reference/morph-helpers.py` (`clone`, `ghost`, `verify`, `final-check`). The shell equivalent is `reference/morph-helpers.sh` — pick one per platform; do not mix.\n\n**When to use helpers vs raw `officecli`.** For 2-3 slide decks, raw commands (recipes a, b) are clearer. For 5+ slides with repeating clone/ghost/verify cadence, helpers save ~40% of commands and provide built-in verification. Every slide is still closed by `officecli validate` before delivery.\n\n### (d) Morph + fade hybrid — entrance on morph slide\n\n**Visual outcome.** A morph pair where `!!scene-ring` moves continuously while a NEW per-slide card fades in simultaneously. Used when a morph-paired backdrop carries the eye and fresh foreground content needs a softer entrance than a raw appearance.\n\n```bash\n# Slide 2 already has transition=morph and !!scene-ring. Add a new card with fade-entrance.\nofficecli add \"$FILE\" /slide[2] --type shape --prop 'name=#s2-card' --prop preset=roundRect \\\n  --prop fill=F5F7FA --prop line=none --prop x=2cm --prop y=12cm --prop width=10cm --prop height=5cm\n\n# Apply simultaneous-with-morph fade entrance to the new card.\n# 'fade-entrance-300-with' = fade in, 300ms, trigger=withPrevious (plays with the morph transition).\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=#s2-card]\" --prop animation=fade-entrance-300-with\nofficecli get \"$FILE\" \"/slide[2]/shape[@name=#s2-card]\" --json | jq '.data.results[0].format.animation'  # readback sanity — drops the trigger suffix, reads back as \"fade-entrance-300\"\n```\n\n**Why this works.** Morph animates the `!!scene-*` shapes only (they have a pair on slide 1); the new `#s2-card` has no slide-1 counterpart, so morph would default-fade it — `fade-entrance-300-with` makes that fade explicit and timed. Keep the animation per pptx v2 floor: ≤ 600ms, no bounce / swivel / fly-from-edge (`officecli help pptx animation` for the canonical preset list).\n\n## Choreography — animation types + staggered timing\n\nHow morph animates multiple shapes determines what the audience sees. Pick the right mechanism for each pair:\n\n| Animation type | How to achieve it (between Slide A and Slide B) |\n|---|---|\n| Simple move | Same `!!` name on both slides, same size, different `x`/`y` — morph interpolates position |\n| Scale transform | Same name, different `width`/`height` — morph interpolates size (and re-positions the center) |\n| Move + scale | Different `x`, `y`, `width`, `height` simultaneously — morph handles all dimensions at once |\n| Color / opacity shift | Same name, different `fill` or `opacity` — morph cross-fades the fill |\n| Rotation | Same name, different `rotation` (degrees) — morph rotates along the shortest arc |\n| Font size change | Same name, different `size` (pt) on text shape — interpolates in PowerPoint 365; less reliable on Keynote / WPS / LibreOffice (may degrade to crossfade). For portable motion, pair `size` change with a matching `width`/`height` delta or an `x`/`y` displacement — the spatial change keeps motion visible when size interpolation drops out |\n| Enter (fade in) | Shape exists only on Slide B (no counterpart on A) — morph fades it in |\n| Exit (fade out) | Shape exists only on Slide A (no counterpart on B) — morph fades it out |\n\n**Multi-shape timing constraint.** All `!!` shapes in one morph pair animate **simultaneously** — there is no per-shape delay / duration knob in the CLI (help confirms: no `morph.duration` / `morph.delay` on slide). To stagger shape A before shape B, **split the transition into two pairs** with an intermediate slide:\n\n```\nSlide 2 → Slide 3:  !!actor-A moves (!!actor-B stays put)\nSlide 3 → Slide 4:  !!actor-B moves (!!actor-A stays put or ghosts)\n```\n\nSlide 3 is an explicit intermediate keyframe. Do NOT attempt to fake staggering via timing props on the shape's `animation=` prop — Morph runs before per-shape animations.\n\n**Good-enough variety heuristic (Best Practice — creative flexibility).** For a morph to read as \"motion\", change at least 3 of {x, y, width, height, rotation, fill, opacity} on the dominant paired shape, with displacement ≥ 5cm OR rotation ≥ 15° OR size delta ≥ 30%. One shape × 3 props is a valid creative pattern (focus on one hero element).\n\n**Delivery Gate 5b-morph-2 is stricter.** The gate hard-asserts ≥ 3 DIFFERENT `!!`-prefixed shapes each vary by ≥ 1 of {x, y, width, height, rotation, font-size} across the pair — integrity check for \"is this really a morph or a pretend-morph\". Heuristic informs creative intent; Gate decides delivery. **Brand-constant scenery (pinned header strip, footer bar, logo badge) does NOT count toward the 3-shape quota** — these are supposed to stay put; motion must come from 3 other named shapes. When in doubt, satisfy the stricter Gate.\n\n**Deck-length rhythm.** Filling every transition with morph reads as anxious, not cinematic. Pace morph moments to deck length:\n- **8-10 slides (dense):** 3-5 morph moments; motion can cluster.\n- **12-18 slides (ceremonial):** 3-5 TOTAL morphs, spaced every 4-6 slides; use `transition=morph` at section dividers so the animation reads as chapter punctuation, not continuous agitation.\n- **18+ slides (Act-based):** structure into 3 acts with 1 long section-divider morph between acts (5-10s of deliberate motion with a brief hold), plus 2-3 quieter morphs inside each act. Lean heavier on `!!scene-*` continuity than per-slide `!!actor-*` churn.\n\n## Scene-actor spatial rule\n\nScene actors and actors moving across the canvas MUST stay in predictable zones during morph — otherwise they cross over content and read as clutter.\n\n**Safe zones (prefer for scene actor rest positions and morph paths):**\n\n```\nTop-right corner:   x ≥ 24cm, y ≤ 6cm\nBottom-right:       x ≥ 24cm, y ≥ 12cm\nBottom-left:        x ≤ 2cm,  y ≥ 12cm\nOff-canvas (ghost): x ≥ 33.87cm  (canvas right edge; use x=36cm for explicit ghost)\n```\n\n**Avoid resting actors in the content core:** `x = 2~28cm, y = 3~16cm`. Actors may **pass through** the core during morph (that's the motion), but they should not end a slide parked there with high opacity unless they are content themselves (`!!actor-*` carrying the slide's message).\n\n**Before placing any scene actor, inspect existing shape bounds:**\n\n```bash\nofficecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n  jq -r '.data.results[0].children[]? | \"\\(.format.name // .path)  x=\\(.format.x) y=\\(.format.y) w=\\(.format.width) h=\\(.format.height)\"'\n```\n\nConfirm the actor's target position does not overlap any `#sN-*` content shape's bounding box (`x` to `x + width`, `y` to `y + height`). If it would overlap, lower actor `opacity` ≤ 0.15 OR move it to a safe zone.\n\n## Style library lookup workflow\n\n`reference/styles/` holds 52 visual style directories (dark / light / warm / vivid / bw / mixed moods) — design inspiration, not templates. Use the library as **on-demand reference**, not as a content dump.\n\n**Why lookup, not copy.** Each of the 52 `build.sh` files is a complete style demo — but the coordinates were hand-tuned for that specific demo's content length. Copying them verbatim into a deck with different content produces overlaps and misalignment (flagged in `INDEX.md` L5-11). The library's value is the **design logic**: palette choice for a mood, signature shape, choreography pattern. Apply that logic to your own grid math.\n\n**Four-step lookup:**\n\n1. **Browse INDEX.** `reference/styles/INDEX.md` groups all 52 styles by palette category and mood (e.g. `dark--premium-navy` = authoritative / refined; `warm--earth-organic` = organic / grounded). The Quick Lookup table also shows each style's **primary hex trio** (bg / fg / accent) — if the user specified a brand color, scan the hex column to find the nearest match without opening every `style.md`. Pick 1 style that matches the topic mood OR aligns with the user-specified hex.\n2. **Read philosophy.** Open `reference/styles/<style-id>/style.md` for design intent — type pairing, color logic, signature elements.\n3. **Glance technique.** Open `reference/styles/<style-id>/build.sh` ONLY for technique reference (signature shapes, palette hex codes, choreography ideas) — **coordinates are known-buggy per `INDEX.md` L5-11**; do not copy them.\n4. **Apply on your own canvas.** Build your deck using pptx v2 grid math + visual floor; borrow only the palette and the signature gesture.\n\n**Pointer:** `→ see reference/styles/<style-id>/` — never inline-copy coordinates from a style build.sh.\n\n## Delivery Gate (inherits pptx v2 + morph additions)\n\n**Gate 1–5a: full port from pptx v2.** → see pptx v2 §Delivery Gate. Schema (whitelisting C-P-2 chart spPr), token grep (`$…$` / `{{…}}` / `\\$\\t\\n` / `()` / `[]`), hyperlink rPr (C-P-1), slide-order sanity, dark-on-dark contrast (Gate 5a). **Refuse to declare done until every pptx Gate 1–5a prints its OK message.** Morph decks have the same token / schema / order risks as any pptx.\n\n### Gate 2 morph addendum — price / metric tokens eaten by zsh\n\nPptx v2 Gate 2 covers `$…$`, `{{…}}`, `\\$\\t\\n` literals, empty `()` / `[]`. Morph decks add a class of leaks: price / metric tokens (`$9/mo`, `$29/month`, `$199/yr`) written in double-quoted `--prop text=\"…\"` — the shell eats `$9` as an empty variable and the CLI stores `/mo` or a stray period. Run this in addition to pptx Gate 2:\n\n```bash\n# Gate 2 morph — price / metric token leaks + stray-period placeholders\n# Pattern hits: bare prices ($9, $29, $9.99), /unit suffix ($9/mo, $199/yr), ${VAR}, \\n/\\r/\\t, lone period\nLEAKS=$(officecli view \"$FILE\" text | grep -nE '\\$[0-9]+(\\.[0-9]+)?(/(mo|month|yr|year|day|wk|week|hr|hour))?|\\$\\{[A-Z_]+\\}|\\\\[nrt]|^\\.$' || true)\nif [ -z \"$LEAKS\" ]; then echo \"Gate 2 morph OK\"; else echo \"LEAK: $LEAKS\"; fi\n```\n\nCovers: `$9` `$9.99` `$29/month` `$199/yr` `$1/day` `${VAR}` `\\n`/`\\r`/`\\t` literals + stray `.` placeholders. Fix: single-quote the prop (`--prop text='$9/mo'`).\n\n### Gate 5b — Visual audit via HTML preview (MANDATORY) — extended for morph\n\nRun `officecli view \"$FILE\" html` and Read the returned HTML path. For every slide, answer the pptx v2 Gate 5b questions (overlap / dark-on-dark / divider overlap / order sanity / missing arrowheads) PLUS these four morph-specific checks:\n\n**Important: selectors with prefix match.** `officecli query` only supports operators `=`, `!=`, `~=`, `>=`, `<=`, `>`, `<` — there is NO `^=` prefix operator. A selector like `shape[name^=!!actor-]` returns an `invalid_selector` error. For \"starts-with\" filtering, use a `get --depth 1` loop + `jq startswith()` as shown below.\n\n- **5b-morph-1 — `!!actor-*` leak into visible area after its section ends.** For every `!!actor-*` that should have exited, confirm `x ≥ 33.87cm` (canvas right edge). Loop + filter (selector-safe):\n  ```bash\n  NSLIDES=$(officecli query \"$FILE\" slide --json | jq '.data.results | length')\n  for N in $(seq 1 $NSLIDES); do\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r --arg n \"$N\" '.data.results[0].children[]? |\n        select(.format.name? // \"\" | startswith(\"!!actor-\")) |\n        select((.format.x // \"0cm\" | rtrimstr(\"cm\") | tonumber) < 33.87) |\n        \"slide \\($n) leak: \\(.format.name) stuck at x=\\(.format.x)\"'\n  done\n  ```\n  Any line printed = actor stuck visible. `final-check` misses this — only the loop + Read HTML do.\n\n- **5b-morph-2 — Adjacent slides have identical spatial composition (no motion).** Hard rule: between every morph pair, ≥ 3 DIFFERENT `!!`-prefixed shapes must each differ by ≥ 1 of {x, y, width, height, rotation, font-size}. Proof loop (dump both slides, diff same-name shapes, count differing shapes):\n  ```bash\n  for K in 1 2 3 4; do\n    A=$(officecli get \"$FILE\" \"/slide[$K]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) |\n        \"\\(.format.name)|\\(.format.x)|\\(.format.y)|\\(.format.width)|\\(.format.height)|\\(.format.rotation // 0)\"')\n    B=$(officecli get \"$FILE\" \"/slide[$((K+1))]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) |\n        \"\\(.format.name)|\\(.format.x)|\\(.format.y)|\\(.format.width)|\\(.format.height)|\\(.format.rotation // 0)\"')\n    VARIES=$(diff <(echo \"$A\") <(echo \"$B\") | grep -c '^[<>]')\n    if [ \"$VARIES\" -lt 6 ]; then echo \"pair $K→$((K+1)) FLAT: only $VARIES diff-lines (need ≥ 6 = 3 shapes × 2 sides)\"; fi\n  done\n  ```\n\n- **5b-morph-3 — Morph-pair name mismatches.** Adjacent slides must share at least 2 `!!`-prefixed names exactly. Proof (note: children live at `.data.results[0].children[]` — bare `.data.children[]` returns null):\n  ```bash\n  for N in 1 2 3 4 5; do\n    echo \"--- slide $N ---\"\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r '.data.results[0].children[]? | select(.format.name? // \"\" | startswith(\"!!\")) | .format.name'\n  done\n  ```\n  Visually compare sequential blocks — shared `!!` names between N and N+1 are the morph pairs. Zero overlap = the pair is a plain fade.\n\n- **5b-morph-4 — `#sN-*` lingering on slide N+1 (ghost leak).** Per-slide content MUST be ghosted (`x=36cm`) on the NEXT slide. Loop + filter per N≥2:\n  ```bash\n  NSLIDES=$(officecli query \"$FILE\" slide --json | jq '.data.results | length')\n  for N in $(seq 2 $NSLIDES); do\n    PREV=$((N-1))\n    officecli get \"$FILE\" \"/slide[$N]\" --depth 1 --json | \\\n      jq -r --arg n \"$N\" --arg p \"$PREV\" '.data.results[0].children[]? |\n        select(.format.name? // \"\" | startswith(\"#s\\($p)-\")) |\n        select((.format.x // \"0cm\" | rtrimstr(\"cm\") | tonumber) < 33.87) |\n        \"slide \\($n) leak: \\(.format.name) stuck at x=\\(.format.x)\"'\n  done\n  ```\n  Any line printed = a `#s(N-1)-*` shape stayed visible on slide N. Ghost it.\n\n**REJECT the delivery** if any 5b-morph-1..4 loop prints a line. Collect stdout from all four loops into one stream and enforce with the COUNT pattern: `LEAK_COUNT=$(...all four loops... | wc -l); if [ \"$LEAK_COUNT\" -gt 0 ]; then echo \"REJECT: $LEAK_COUNT morph leaks\"; else echo \"Gate 5b-morph OK\"; fi`.\n\n## Renderer honesty\n\n**Morph renders in:** PowerPoint 365 (Windows/Mac), Keynote, WPS, PowerPoint Online.\n\n**Morph does NOT render in:** LibreOffice Impress (renders static, sometimes as fade), Google Slides web viewer (loses interpolation), most HTML / SVG viewers, `officecli view html` (structural only — morph is runtime). This is `[RENDERER-BUG]`, not a skill defect. Tell the user explicitly: \"Open in PowerPoint 365 / Keynote / WPS to see the morph motion; other viewers will show static or plain fade.\"\n\nStatic screenshots from any renderer **cannot verify morph motion** (the motion only exists at runtime). Use Gate 5b queries above to prove pair correctness; use a live viewer to prove motion quality.\n\n## Ghost Discipline & Actor Lifecycle\n\n**Every `!!actor-*` and `#sN-*` shape must be managed across EVERY slide, not just its \"exit\" slide.**\n\n### The Per-Slide Ghosting Rule\n\nWhen building a multi-slide morph deck:\n1. **Slide N: Introduce `!!actor-ring` (visible at x=0cm)**\n2. **Slide N+1: Add new content. Before finishing, ghost `!!actor-ring` to `x=36cm`.**\n3. **Slide N+2: Add more content. Re-ghost `!!actor-ring` to `x=36cm` again.** (Not optional — even though it was already off-screen, each slide is a fresh canvas.)\n4. **Slide N+3: If `!!actor-ring` should be visible again, move it back to x=0cm or its new position.**\n\n**Why:** Each slide's shape list is independent. Moving a shape off-canvas on slide N does NOT carry over to slide N+1 — if you forget to re-ghost it, it will re-appear at its original position on N+1.\n\n### Workflow Pattern (Bash)\n\n```bash\n# After adding new content shapes to slide $SLIDE:\nfor ACTOR in \"!!actor-ring\" \"!!actor-dot\" \"!!actor-accent-bar\"; do\n  officecli set \"$FILE\" \"/slide[$SLIDE]/shape[@name=$ACTOR]\" --prop x=36cm || true\ndone\n```\n\nOr in a build loop:\n\n```bash\nfor SLIDE_NUM in 3 4 5 6 7 8 9 10 11; do\n  # Add content specific to this slide\n  officecli add \"$FILE\" \"/slide[$SLIDE_NUM]\" --type shape ...\n  \n  # IMMEDIATELY ghost all old actors (M-2 prevention)\n  officecli set \"$FILE\" \"/slide[$SLIDE_NUM]/shape[@name=!!actor-ring]\" --prop x=36cm || true\n  officecli set \"$FILE\" \"/slide[$SLIDE_NUM]/shape[@name=!!actor-dot]\" --prop x=36cm || true\ndone\n```\n\n### Detection: Ghost Count Gate\n\n`morph-helpers.py final-check` counts all shapes at `x ≥ 34cm`. If count > 50, it prints:\n```\nREJECT: Found 135 accumulated ghosts — likely M-2 ghost accumulation.\nRun: officecli query deck.pptx 'shape[x>=34cm]' --json | jq '.data.results | length'\nExpected ≤ 50 (roughly 4–5 active actors × 10–12 slides).\n```\n\n**Fix:** Review the build log, ensure every slide re-ghosts all actors that should not appear in it. Re-run final-check. If still > 50, use `morph-helpers.py clean-accumulation deck.pptx` (see reference section).\n\n## Common Morph Pitfalls (design + workflow traps)\n\nBase pptx pitfalls (shell quoting, zsh `[N]` globbing, hex `#` prefix, `\\n` in prop text) → see pptx v2 §Common Pitfalls. These are the morph-specific traps:\n\n| Pitfall | Correct approach |\n|---|---|\n| `!!scene-card` and `!!actor-card` in the same deck | Names must be unique across prefixes. Rename: `!!scene-card-bg` vs `!!actor-card-content` |\n| Renaming shapes mid-build after some slides are already done | Ghost accumulation bug waiting to happen. Stop, redraw the §Morph Pair Planning table, rerun affected slides |\n| Placing `!!actor-*` into the content core without planning an exit | Every `!!actor-*` needs a ghost slide. Plan it in the pair table BEFORE coding |\n| **Ghost accumulation (M-2): forgetting to re-ghost `!!actor-*` on later slides** | **CRITICAL:** When you add new content to slide N+1, ALL `!!actor-*` from slide N that should not be visible must be moved to `x=36cm` again. Do NOT assume they stay off-screen once ghosted — each slide is independent. Build pattern: `for each new slide: add content shapes → then loop: set each active !!actor-* to x=36cm`. `morph-helpers.py final-check` will REJECT if ghost count exceeds 50. |\n| Forgetting `transition=morph` on a slide | Silent fade. Gate 5b-morph-2 (no motion) catches it; fix via `set /slide[N] --prop transition=morph` |\n| Assuming `@name=` paths break on a morph slide | They do not — `@name=` still resolves after `transition=morph` (M-1); only the *readback* name gains a `!!` prefix |\n| Adjacent slides visually identical | Morph has nothing to interpolate — collapses to plain fade. Apply §Scene-actor spatial rule and move ≥ 3 shapes by ≥ 5cm / ≥ 15° |\n| Trying to stagger 2 shapes via per-shape timing | Not supported — split the pair into two transitions with an intermediate keyframe slide |\n| Testing morph motion in LibreOffice or a browser | `[RENDERER-BUG]`, not skill defect. Test in PowerPoint 365 / Keynote / WPS |\n| Deleting a `!!` shape on exit instead of ghosting it | Deletion breaks morph pairing — the shape vanishes without animation. Always ghost to `x=36cm` |\n| Writing `--prop text=\"$9/mo\"` with double quotes | Shell eats `$9` as empty variable → text stored as `/mo` or stray `.`. Use single quotes: `--prop text='$9/mo'`. Gate 2 morph addendum greps this leak. |\n| Using `<a:br/>` literal inside `--prop text='line1<a:br/>line2'` | Stored as 7 literal characters, not a line break. Use `officecli add \"/slide[N]/shape[@id=K]\" --type paragraph` once per line (M-6). |\n| Using `shape[name^=!!actor-]` selector | `officecli query` has no `^=` operator — returns `invalid_selector`. Use `get /slide[N] --depth 1 --json \\| jq '.data.results[0].children[]? \\| select(.format.name \\| startswith(\"!!actor-\"))'`. |\n\n## Known Issues & Pitfalls\n\nBase pptx bugs C-P-1..7 (hyperlink rPr, chart ChartShapeProperties warning, animation duration readback, animation remove, connector enum, connector `@name=`, chart-color renderer normalization) all apply. **→ see pptx v2 §Known Issues C-P-1..7 for workarounds.**\n\n**Morph-specific (M-1..5):**\n\n| # | Symptom | Workaround |\n|---|---|---|\n| **M-1** | After `officecli set '/slide[N]' --prop transition=morph`, every shape on that slide has `!!` auto-prepended to its name (`#s1-title` → `!!#s1-title`). The readback name is the prefixed form. **Selector filter caveat:** after auto-prefix, `!!#sN-caption` coexists alongside `!!actor-*` — filtering \"scene actors\" in `jq` with `startswith(\"!!\")` produces false matches on auto-prefixed content. Always filter with `startswith(\"!!actor-\")` or `startswith(\"!!scene-\")`, never bare `startswith(\"!!\")`. | `@name=` path selectors **still resolve** — `/slide[N]/shape[@name=#s1-title]` returns the shape (matching is suffix/prefix-tolerant), so no path rewrite is needed. When you need the prefixed name in a jq filter, account for the leading `!!`. |\n| **M-2 🚨** | **Ghost accumulation — `!!actor-*` introduced on slide 3 stays visible on slides 4, 5, 6 unless EXPLICITLY ghosted every page.** `final-check` helper detects this and rejects if ghost count > 50. | **MANDATORY per-slide rule:** After you add new content to a slide, immediately set ALL active `!!actor-*` from previous slides to `x=36cm` (or explicitly position them visible if they belong in the current context). Example: `officecli set /slide[4]/shape[@name=!!actor-ring] --prop x=36cm`. Run after EVERY slide addition, not just at the end. See §Ghost Discipline & Actor Lifecycle below. |\n| **M-3** | Section-transition boundary — on the first slide of a new topic section, previous-section `!!actor-*` shapes visibly linger. No command errors; only visual clutter. | On every section-start slide, explicitly ghost ALL `!!actor-*` from the previous section to `x=36cm`. Scene shapes (`!!scene-*`) stay. |\n| **M-4** | Agents sometimes invent `morph.duration=` / `transition.delay=` as independent props — they are rejected as UNSUPPORTED. | Use the combined shorthand on the `transition` prop: `transition=morph-slow` / `-fast`, or `transition=morph-<DUR_MS>` (e.g. `transition=morph-1500`). Both round-trip on readback (`transitionSpeed=slow`, `transitionDuration=1500`). Only beyond what the shorthand exposes (fine control of the raw XML), fall back to `raw-set` on `<p:transition>` — see M-4 example block below. |\n| **M-5** | `[RENDERER-BUG]` LibreOffice / Google Slides web viewer render morph slides as plain fade (no interpolation). | Test in PowerPoint 365 / Keynote / WPS. Not a skill defect — do not chase. |\n| **M-6** | `<a:br/>` written inside `--prop text='line1<a:br/>line2'` is stored as the literal 7-character string, NOT interpreted as a line break. Audience sees `line1<a:br/>line2` rendered verbatim. | For multi-line bullets / captions, add one paragraph per line: `officecli add \"/slide[N]/shape[@id=K]\" --type paragraph --prop text='line1'` then repeat with `text='line2'`. See pptx v2 §Shell escape for the real-newline workflow. |\n\n**M-4 example — slow down all morph transitions, raw-set fallback** (prefer `transition=morph-slow`; use this only for control beyond the shorthand). Note `//p:transition` matches both `mc:Choice` and `mc:Fallback` on a morph slide, yielding `2 element(s) affected`:\n\n```bash\n# Per-slide: add spd=\"slow\" to every transition element on slide N (2 XML hits per morph slide)\nfor N in 2 3 4; do\n  officecli raw-set \"$FILE\" \"/slide[$N]\" --xpath \"//p:transition\" --action setattr --xml 'spd=slow'\ndone\nofficecli validate \"$FILE\"\n```\n\nReadback: `officecli query \"$FILE\" slide --json | jq '.data.results[].format | select(.transition==\"morph\") | .transitionSpeed'` prints `\"slow\"` for each affected slide.\n\n## Outputs & delivery\n\nEvery morph deck ships with three artifacts, each as a standalone file:\n\n1. `<topic>.pptx` — the deck, closed + `officecli validate` clean (Delivery Gate 1 OK).\n2. `build.sh` or `build.py` — the re-runnable script (bash for shell-native builds; Python for multi-slide arcs using `morph-helpers.py`). Must recreate the deck from a fresh `officecli create` call.\n3. `brief.md` — **standalone file, NOT embedded in anything else.** Contains:\n   - Section 1: topic / audience / purpose / narrative / style direction (1 named style from `reference/styles/INDEX.md`)\n   - Section 2: slide-by-slide outline (page type + one-sentence argument per slide)\n   - Section 3: §Morph Pair Planning table (Pair / Slide A / Slide B / Actors / Ghosts) — the design record the reviewer needs to audit choreography\n\n**Pre-deliver reminder to the user (verbatim-safe wording):**\n\n- \"The deck is ready with morph transitions. Open it in PowerPoint 365 / Keynote / WPS to see the motion — LibreOffice and web viewers render static.\"\n- \"While the build script is running, the `.pptx` may be rewritten several times. If you want to preview progress, use `officecli watch \"$FILE\"` and open the live preview in AionUi — do NOT click 'Open with system app' during the build, or you'll hit a file lock.\"\n\n## Adjustments after creation\n\nStandard adjustments table → see pptx v2 §Common Pitfalls / `swap` / `move` / `remove` / `set`. Morph caveat: **after any `swap` or `move` that reorders morph-paired slides, re-verify the adjacency of shared `!!` names.** Run Gate 5b-morph-3 query above on the affected pairs — if the swap broke a pair, either rename shapes or re-choreograph the transition.\n\n**Final sanity check before delivery.** Run the full Delivery Gate (1 through 5b-morph-1..4), open the `.pptx` in PowerPoint 365 / Keynote / WPS, watch one full slide-to-slide morph to confirm motion is visible. If any Gate prints REJECT, fix and re-run — never deliver with a known-open gate.\n\n## References\n\n- `reference/decision-rules.md` — Pyramid Principle, SCQA, page-type menu, `brief.md` schema. Read during §Morph Pair Planning to decide narrative arc before writing commands.\n- `reference/pptx-design.md` — residual design notes (Scene Actors mechanics, page-type table, choreography patterns). Canvas / fonts / colors live in pptx v2 — this file covers only the morph-unique material.\n- `reference/morph-helpers.py` — Cross-platform (Mac / Windows / Linux) Python helpers for clone + ghost + verify + final-check. Import as a library or call via CLI args. Preferred for 5+ slide arcs.\n- `reference/morph-helpers.sh` — Bash equivalent. Pick one per project; do not mix.\n- `reference/styles/INDEX.md` — 52-style visual library, grouped by palette (dark / light / warm / vivid / bw / mixed) and mood. Lookup workflow in §Style library lookup workflow above.\n- `skills/officecli-pptx/SKILL.md` — base pptx v2 rules (visual floor, grid, canonical palettes, chart-choice, connector canon, Delivery Gate 1–5a, Known Issues C-P-1..7, Shell escape 3-layer).\n","frontmatter":{"name":"morph-ppt","description":"Use this skill when the user wants a .pptx with smooth cross-slide animation — PowerPoint Morph transitions, Keynote-style continuous motion, shapes that grow / move / rotate as the slide advances. Trigger on: 'morph', 'morph transition', 'smooth transition', 'continuous animation across slides', 'Keynote-style transition', 'animated slide sequence', 'shape continuity across slides'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate 1–5a). DO NOT invoke for a generic deck, pitch deck, or board review without cross-slide motion — route those to officecli-pptx base or officecli-pitch-deck."},"isInternal":false,"tokens":13197,"sizeBytes":48768},{"name":"SKILL.md","path":"skills/officecli-academic-paper/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-academic-paper/SKILL.md","title":"officecli-academic-paper","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-academic-paper\ndescription: \"Use this skill to build academic-style .docx output: journal / conference / thesis chapters carrying formal citation style (APA, Chicago, IEEE, MLA), numbered equations, figure & table cross-references, footnotes/endnotes, bibliography, or multi-column journal layout. Trigger on: 'research paper', 'journal paper', 'conference paper', 'manuscript', 'thesis', 'APA', 'MLA', 'Chicago', 'IEEE two-column', 'bibliography', 'hanging indent', 'citation style', 'abstract + keywords', 'equation numbering', 'cross-reference', paper with footnotes/endnotes. Output is a single .docx.\"\n---\n\n# OfficeCLI Academic Paper Skill\n\n**This skill is a scene layer on top of `officecli-docx`.** Every docx hard rule — style architecture, heading hierarchy, shell quoting, page-break rules, live PAGE field, Delivery Gate, renderer quirks — is inherited, not re-taught. This file adds only what academic papers need on top: citation styles, equations, SEQ / PAGEREF cross-refs, multi-column journal layout, bibliography hanging indent, abstract/keywords/affiliation block.\n\nWhen the docx base rules cover it, the text here says `→ see docx v2 §X`. Read docx v2 first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what an academic paper requires, not every command flag.** When a prop name, enum value, or field instruction is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help docx                          # All docx elements\nofficecli help docx <element>                # Full schema (e.g. section, equation, field, footnote)\nofficecli help docx <element> --json         # Machine-readable\n```\n\nHelp is pinned to the installed CLI version. **When this skill and help disagree, help wins.** Every `--prop X=` in this file has been grep-verified against `officecli help docx <element>` — if help adds / renames a prop in a later version, trust help.\n\n## Mental Model & Inheritance\n\n**Inherits docx v2.** You should have read `skills/officecli-docx/SKILL.md` first. This skill assumes you know how to add paragraphs, set styles, build tables, insert images, manage TOC/footer/headers, force page breaks, and run the Delivery Gate. If any of those are unfamiliar, open a second session on docx v2 before continuing.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see docx v2 §Shell & Execution Discipline. The same rules apply here verbatim — quote `[N]` paths, single-quote any value containing `$` (including `$2.8B` in a body paragraph or `@` DOIs), never hand-write `\\$ \\t \\n` in executable examples, one command at a time. Academic-paper examples below use `$FILE` as a shell variable (`FILE=\"thesis.docx\"`).\n\n## What \"academic\" means here (identity)\n\nAn academic paper is a docx with a **scholarly layer** on top: verifiable citations, precise equations, cross-refs that stay in sync, a formatted reference list. The base docx rules still apply; academic adds six deltas:\n\n1. **Citation style is a contract.** APA / Chicago / IEEE / MLA each dictate author format, date placement, reference-list order, in-text marker shape. Pick one at the start; every later decision (hanging indent, footnote vs parenthetical, `[1]` vs `(Smith, 2024)`) follows.\n2. **Equations are first-class content** — inline `oMath` inside prose, display `oMathPara` as standalone blocks, optionally numbered.\n3. **Figures and tables auto-number.** `SEQ Figure` / `SEQ Table` fields count them; `PAGEREF` links \"see Fig. 2\" to its live page number.\n4. **Bibliography uses hanging indent** (first line flush left, continuation lines indented). Not first-line indent. Not left indent alone. Hanging.\n5. **Abstract / keywords / affiliation block** is a first-page three-piece, not a cover in the marketing sense. Block-style abstract, no first-line indent, no decoration.\n6. **Multi-column layout** appears in IEEE / ACM / Nature / many journals: single-column abstract + two-column body.\n\n### Reverse handoff — when to go BACK to docx\n\nStay in **docx v2** for white papers, policy briefs, technical reports, HR templates — anything without a venue / citation style. Use **this skill** only when the document will carry at least TWO of: citation-style biblio, equations, SEQ/PAGEREF cross-refs, multi-column, abstract + keywords block.\n\n## Workflow — 5 verbs\n\n1. **Read the venue spec.** APA 7 / Chicago 17 / IEEE / MLA 9 / journal-specific. Line spacing, font, citation shape, biblio sort order — everything downstream follows from this one decision.\n2. **Plan the sections.** Abstract → keywords → introduction → methods → results → discussion → conclusion → references. Estimate heading count for TOC decision (3+ headings = add a TOC, see docx v2 §Table of Contents).\n3. **Set styles up front.** Heading1 / Heading2 / Heading3 / Caption / AbstractTitle / Bibliography. Define all styles BEFORE any content (→ see docx v2 §Paragraphs and styles — same rule here, same failure mode if skipped).\n4. **Build body in order.** Cover / title block → abstract → keywords → TOC (if needed) → body sections in reading order → figures / tables with SEQ captions → bibliography → footnotes are added last by paragraph path.\n5. **QA — Delivery Gate.** Inherit docx v2 Gates 1-3, then add academic Gates 4-5 below.\n\n## Requirements (academic floor on top of docx v2)\n\nEverything in docx v2 §Requirements for Outputs applies. On top of that, academic papers MUST meet these additional rules:\n\n### Typography and spacing (venue-aware)\n\n- **Font.** Times New Roman 11-12pt body (default) or venue-specified (IEEE uses Times 10pt 2-col; APA allows Calibri 11pt). Same body font throughout; no decorative heading fonts.\n- **Heading hierarchy.** H1 = 20pt bold, H2 = 14pt bold, H3 = 12pt bold italic, body = 11-12pt. (Same numbers as docx v2 — restated because academic papers never rely on Word defaults.)\n- **Line spacing.** APA 7 = 2x (double). Chicago / IEEE / most journals = 1.5x. Never below 1.15x. Set on body paragraphs and on References.\n- **Margins.** 1 inch (1440 twips) all sides unless the venue says otherwise (some journals require 1.25in left for binding — check the spec).\n\n### Abstract, bibliography, caption placement\n\n- **Abstract is block-style.** NO `firstLineIndent`. Use `spaceAfter=12pt` for paragraph separation. If `view issues` reports \"body paragraph missing first-line indent\" on an Abstract paragraph, it's a false positive — ignore.\n- **Bibliography uses hanging indent.** Each entry is one paragraph with `indent=720 hangingIndent=720` (left indent 0.5\", first-line reversed by same amount). First line flush left; wraps indent under author name.\n- **Figure captions go BELOW the figure.** Table captions go ABOVE the table. This is the single rule most non-academics get wrong — APA, Chicago, IEEE, MLA all agree on it.\n- **Citation round-trip.** Every in-text citation key must resolve to an entry in the reference list. Delivery Gate 4 verifies.\n- **SEQ presence.** Any paper with numbered figures or tables must carry live `SEQ Figure` / `SEQ Table` fields (not hardcoded \"Figure 1\" text that drifts when you insert a new figure mid-document). Delivery Gate 5 verifies.\n\n### Cover / first-page block\n\nAcademic covers differ from professional covers. Minimum elements: title (centered, 20-22pt bold), author(s), affiliation, submission target or journal, date, abstract, keywords. The \"60% fill\" rule from docx v2 §Visual delivery floor still applies — a three-line cover with half a page of whitespace is a fail. See §Abstract / keywords / affiliation block below for the first-page recipe.\n\n### Section numbering convention (STYLE-DEPENDENT — do not apply blindly)\n\nAcademic section numbers are **part of the heading text**, not computed via list numbering. `officecli`'s `numId`/`listStyle` mechanism is fragile across Heading1 re-use, so hand-write the prefix. BUT the prefix shape varies by style — DO NOT use the same form for all four:\n\n| Style | H1 format | H2 format | Example |\n|---|---|---|---|\n| **APA 7** | **UNNUMBERED centered bold** | Unnumbered left-aligned bold | `Introduction` / `Methods` (centered) |\n| **Chicago** | `\"N. Title\"` left-aligned | `\"N.M Title\"` | `1. Introduction`, `2.1 Policy Formation` |\n| **IEEE** | `\"N. TITLE\"` ALL CAPS + Roman numerals | `A. Subtitle` title case | `I. INTRODUCTION`, `II. RELATED WORK`, `A. Datasets` |\n| **MLA 9** | Unnumbered left-aligned bold | Same | `Literature Review` (no prefix) |\n\nAPA 7 L1 headings are **centered, bold, unnumbered**; L2 are flush-left bold; L3 flush-left bold italic; L4/L5 run-in. Do NOT prefix APA headings with `1. / 2.` — that is Chicago/IEEE convention. IEEE wants ALL CAPS with Roman numerals (`I. INTRODUCTION`); inside each section, use `A./B./C.` sub-headings (title case). Arabic-numbered body sections are Chicago-style only.\n\n**Exception for all four**: References / Bibliography / Works Cited / Acknowledgments are unnumbered regardless of style — omit the `N.` prefix.\n\n## Quick Start — minimal APA paper\n\n```bash\nFILE=\"paper.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" / --prop defaultFont=\"Times New Roman\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote Work and Team Cohesion\" --prop align=center --prop size=20pt --prop bold=true --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen\" --prop align=center --prop size=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Department of Psychology, Stanford University\" --prop align=center --prop size=11pt --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Abstract\" --prop align=center --prop size=14pt --prop bold=true --prop spaceBefore=12pt --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"This study examines remote-work adoption on team cohesion across 18 months...\" --prop size=12pt --prop lineSpacing=2x --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Keywords: remote work, team cohesion, psychological safety\" --prop italic=true --prop size=11pt --prop spaceAfter=18pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"1. Introduction\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote-work research (Smith, 2024) has expanded since 2020...\" --prop size=12pt --prop lineSpacing=2x --prop firstLineIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"References\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Smith, J. (2024). Remote work and cohesion. Journal of Applied Psychology, 109(3), 412-430.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=10pt --prop field=page\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nTen-line skeleton. Real papers grow by adding more body paragraphs, more bibliography entries (each with the same `indent=720 hangingIndent=720` pair), figures / tables with captions, and a TOC if there are 3+ Heading1s. The Quick Start validates clean; the sections below elaborate each dimension.\n\n## Citation style recipes\n\nFour mainstream families. Pick one at project start; every downstream decision follows. **Per-style decision table:**\n\n| Style | In-text shape | Reference list order | Body line spacing | Footnotes? |\n|---|---|---|---|---|\n| APA 7 | `(Smith, 2024)` or `Smith (2024)` | Alphabetical by author | 2x (double) | Rare (content notes only) |\n| Chicago 17 (Notes-Bib) | Superscript footnote number | Alphabetical by author | 1.5x-2x | **Primary** (full citation in footnote) |\n| IEEE | `[1]`, `[2]`, ..., `[N]` | Order of first citation | 1.15x-1.5x, 2-col | Rare |\n| MLA 9 | `(Smith 412)` page-number | Alphabetical by author, \"Works Cited\" | 2x | Rare |\n\nShared defaults across all four: reference-list paragraphs use `indent=720 hangingIndent=720` (hanging indent 0.5\"); add a live TOC if 3+ Heading1s (→ see docx v2 §Table of Contents); set `updateFields=true` and report TOC page numbers as uncomputed until a Word-compatible field engine updates them.\n\n### APA 7 (social sciences — psychology, education, management)\n\n- In-text: `(Author, Year)` or `Author (Year)` for narrative. Page number required on direct quotes: `(Smith, 2024, p. 15)`. Three+ authors: `(Smith et al., 2024)` after first citation.\n- Reference list order: **alphabetical by first author's surname**. Title caps: sentence case for article titles, title case for journal names (italic).\n- Reference shape: `Author, A. A., & Co-Author, B. B. (Year). Title of article. Journal Name, Volume(Issue), pages.` DOI preferred over URL; present as https URL, not `doi:` prefix.\n- Double-space everything (`lineSpacing=2x`) including abstract and references. Body first-line indent = 0.5\" (`firstLineIndent=720`).\n\n```bash\n# Body paragraph with parenthetical citation\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Remote work adoption accelerated during the pandemic (Kramer & Kramer, 2020).\" --prop size=12pt --prop lineSpacing=2x --prop firstLineIndent=720\n# Reference entry with hanging indent\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Kramer, A., & Kramer, K. Z. (2020). The potential impact of the Covid-19 pandemic on occupational status. Journal of Vocational Behavior, 119, 103442.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\n# DOI hyperlink appended to the reference paragraph\nofficecli add \"$FILE\" \"/body/p[last()]\" --type hyperlink --prop url=\"https://doi.org/10.1016/j.jvb.2020.103442\" --prop text=\"https://doi.org/10.1016/j.jvb.2020.103442\"\n```\n\nQA: `officecli query \"$FILE\" 'paragraph[hangingIndent]'` returns every reference entry; zero references with first-line indent instead of hanging.\n\n### Chicago 17 — Notes-Bibliography (humanities — history, philosophy, religion)\n\n- In-text: superscript footnote number; full citation in the first footnote (`Timothy Brook, The Troubled Empire (Cambridge, MA: Harvard UP, 2010), 142.`); **shortened form** thereafter (`Brook, Troubled Empire, 150.`).\n- **Repeat-citation rule (Chicago 17, op. cit. deprecated):**\n  - **Immediately-consecutive** citation of **the same source, same page** → `Ibid.`\n  - **Immediately-consecutive, different page** of same source → `Ibid., 22.`\n  - Non-consecutive repeat → **shortened form** (`Brook, Troubled Empire, 150.`), NOT `op. cit.`. Chicago 17 drops `op. cit.` — use shortened form every time except for immediate repeats.\n- Bibliography at end, **alphabetical by first author's surname** (\"Brook, Timothy.\"), hanging indent. Footnote body renders at the viewer's footnote default (typically 10pt); bibliography entries 12pt. (The `footnote` element exposes only `text` — size is not settable per-footnote; trust renderer defaults.)\n- Typical split for primary-source-heavy papers: `Primary Sources` and `Secondary Sources` as two Heading2s under a single `Bibliography` Heading1. Book titles italic in both footnotes and bibliography.\n- Chicago also has an Author-Date variant used in the sciences — if the venue specifies Chicago Author-Date, fall back to the APA recipe and change only the punctuation (no comma between author and year: `(Smith 2024)`).\n\n```bash\n# Body paragraph that will anchor a footnote, then the footnote itself\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"The Ming dynasty's 海禁 policy shaped coastal trade for two centuries.\" --prop size=12pt --prop lineSpacing=1.5x --prop firstLineIndent=720\nofficecli add \"$FILE\" \"/body/p[last()]\" --type footnote --prop text=\"Timothy Brook, The Troubled Empire: China in the Yuan and Ming Dynasties (Cambridge, MA: Harvard University Press, 2010), 142.\"\n# Next footnote — shortened form\nofficecli add \"$FILE\" \"/body/p[last()]\" --type footnote --prop text=\"Brook, Troubled Empire, 150.\"\n# Bibliography section split — primary sources first\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Bibliography\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Primary Sources\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Ming Shilu 明實錄. Taipei: Academia Sinica, 1966.\" --prop size=12pt --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Secondary Sources\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Brook, Timothy. The Troubled Empire: China in the Yuan and Ming Dynasties. Cambridge, MA: Harvard University Press, 2010.\" --prop size=12pt --prop indent=720 --prop hangingIndent=720\n```\n\nQA: `officecli query \"$FILE\" 'footnote'` count ≥ body-paragraph citation count.\n\n### IEEE (engineering — transactions, conference proceedings)\n\n- In-text: `[1]`, `[2]`. Numbered in **order of first appearance**, not alphabetical. Reuse the same number for repeat citations. `[1, p. 15]` for page refs, `[1]-[3]` for a range.\n- Reference entry starts with the bracketed number: `[1] A. Smith and B. Jones, \"Title,\" IEEE Trans. X, vol. 5, no. 3, pp. 1-10, 2024, doi: ...`. Authors are initial-first; journal names abbreviated per IEEE list (`IEEE Trans. Neural Netw.`, not full name).\n- Body is **two-column** (see §Multi-column below). Abstract is single-column above the fold, 10pt, 1.15x line spacing, typically 200-250 words.\n- First-line indent on body paragraphs = 0.2\" (`firstLineIndent=288` twips ≈ 14pt). Smaller than APA's 0.5\" because the 2-col width is narrower.\n- **Section headings: ALL CAPS with Roman numerals** — `I. INTRODUCTION`, `II. RELATED WORK`, `III. METHOD`. Sub-sections `A. Datasets`, `B. Baselines` in title case. Do NOT use `1. Introduction` (Arabic) for IEEE — that is Chicago style.\n- **Tables are numbered Roman**: `Table I`, `Table II`, `Table III`. Figures remain Arabic (`Fig. 1`, `Fig. 2`). `recalcFields=seq` writes Arabic cached values for both — for IEEE Roman tables, either patch the cached `<w:t>` to Roman manually after recalc, or accept Arabic and note it in the cover letter.\n\n```bash\n# Body citing reference 1\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Attention-based anomaly detection has been applied to industrial sensor data [1], [2].\" --prop size=10pt --prop lineSpacing=1.15x\n# Reference list entry — number in the text\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"[1] A. Smith and B. Jones, \\\"Attention for anomaly detection,\\\" IEEE Trans. Neural Netw., vol. 35, no. 2, pp. 412-430, 2024.\" --prop size=10pt --prop indent=720 --prop hangingIndent=720\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"[2] C. Lee, \\\"Time-series anomaly survey,\\\" in Proc. ICML, 2023, pp. 1200-1215.\" --prop size=10pt --prop indent=720 --prop hangingIndent=720\n```\n\nQA: the highest `[N]` in body must equal the number of reference-list entries. Grep: `officecli view \"$FILE\" text | grep -oE '\\[[0-9]+\\]' | sort -u | tail -5`.\n\n### MLA 9 (literature, languages, cultural studies)\n\nDiff vs APA: in-text is `(Author Page)` **no comma** (e.g. `(Smith 412)`); direct quotes always carry the page number. Reference section titled **Works Cited** (not References / Bibliography). Entries alphabetical by surname, hanging indent, 2x spacing, nine \"core elements\" separated by periods: `Author. Title. Container, Other Contributors, Version, Number, Publisher, Date, Location.` — skip any that don't apply. Book titles italic; article titles in quotes. Otherwise identical to APA paragraph setup.\n\n## Equations (OMML — inline vs display)\n\n`--type equation` parses a LaTeX-ish formula into OMML. Two modes, selected by `--prop mode=`:\n\n| Mode | XML | Visual | Use |\n|---|---|---|---|\n| `display` (default) | `<m:oMathPara>` at `/body` | Standalone centered block | Numbered equations, theorem statements |\n| `inline` | `<m:oMath>` appended to a run inside a paragraph | Runs with the text | `if $x > 0$` style in prose |\n\n```bash\n# Display equation (own paragraph, centered) — explicitly set mode=display for clarity\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"x^2 + y^2 = z^2\"\n# Display equation with Greek / subscript / integral — verify rendering below\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"\\\\lambda_1 + \\\\alpha\"\nofficecli add \"$FILE\" /body --type equation --prop mode=display --prop formula=\"\\\\frac{1}{2\\\\pi} \\\\int_0^{\\\\infty} e^{-x^2} dx\"\n# Inline equation INSIDE prose — required whenever variables like x_{t+1}, \\lambda, etc. appear in a body paragraph:\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Given the weight \" --prop size=11pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type equation --prop mode=inline --prop formula=\"W_t\"\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\" we define the loss...\"\n```\n\n**Verify equations render as OMML math**, not plain-text LaTeX tokens. After `close`, run:\n```bash\nofficecli view \"$FILE\" text | head -20       # λ₁ + α, ∫₀∞, x² must appear as unicode math (verified renders)\nofficecli raw \"$FILE\" /document | grep -c '<m:oMathPara'   # ≥ 1 per display equation\n```\nIf the body prose contains raw `lambda_1`, `x_{t+1}`, `\\alpha` or similar plain-text tokens (i.e., you typed them into a `paragraph --prop text=` instead of wrapping with `--type equation --prop mode=inline`), downstream viewers will render them as literal ASCII. **Rule: every mathematical variable / Greek letter / subscript in prose goes through `--type equation mode=inline`, never through `paragraph --prop text=`.**\n\n**LaTeX subset pitfalls** (non-negotiable):\n\n1. `\\left(...\\right)` / `\\left[...\\right]` with a sub/superscript **inside** the delimiters → parse error (`Error: cast object … Subscript`). An OUTER script (`\\left(x+y\\right)^2`) is fine; plain `(`, `)`, `[`, `]` always work and OMML auto-sizes them in display mode.\n2. `move` on `/body/oMathPara[N]` reorders the display equation (it repositions the wrapping paragraph). `--before <path>` may leave a stray empty paragraph; prefer `--index` or `--after` for a clean reorder.\n\n**Equation numbering** — no native `\\eqno`. The journal-standard layout is **one line**: equation centered, number flush-right at the column edge (`Y = A(X) ⊗ X      (1)`). Build it with two paragraph **tab stops** — a `center` tab at the column mid-point and a `right` tab at the column right edge — then lay out `[tab] equation(inline) [tab] (1)` in a single paragraph. Do NOT use a centered display equation followed by a separate right-aligned `(1)` line — that splits the number onto its own line and is the most common reason agents fail to reproduce the expected look.\n\n```bash\n# Tab positions depend on the COLUMN width (twips). Default blank doc = A4, 3.18cm margins\n#   → text width = 8300 twips.\n#   single column: center tab = 4150, right tab = 8300\n#   two columns (default 720-twip gutter): col = (8300-720)/2 = 3790 → center 1895, right 3790\n# (other page size / margins: text width = pageWidth - marginLeft - marginRight; recompute.)\nofficecli add \"$FILE\" /body --type paragraph                                   # the equation paragraph (say it lands at p[N])\nofficecli add \"$FILE\" \"/body/p[N]\" --type tab --prop pos=4150 --prop val=center\nofficecli add \"$FILE\" \"/body/p[N]\" --type tab --prop pos=8300 --prop val=right\nofficecli add \"$FILE\" \"/body/p[N]\" --type run --prop text=$'\\t'                 # tab → jump to center\nofficecli add \"$FILE\" \"/body/p[N]\" --type equation --prop mode=inline --prop formula='Y = A(X) \\otimes X'\nofficecli add \"$FILE\" \"/body/p[N]\" --type run --prop text=$'\\t(1)'             # tab → jump to right edge, then the number\n```\n\nFor a two-column section just use the two-column tab positions (1895 / 3790); the same paragraph then centers the equation within its column with `(1)` at the column's right edge. Schema: `officecli help docx tab`.\n\n**Do NOT place `--type equation` directly on a table cell `tc[N]` path** — it is rejected with a guard error (`table cells only accept paragraphs, tables, or SDTs`), so no bad XML is written. Target `tc[N]/p[1]` with `mode=inline` if you need equations in cells.\n\nFull equation schema: `officecli help docx equation`.\n\n## Figures, tables, and cross-references (SEQ + PAGEREF)\n\nTwo primitives, both **native fieldTypes** (`officecli help docx field` for the enum): `seq` for auto-numbered caption counters, `pageref` for \"see Fig. 2 on page 7\" back-references. Native fields insert with an unevaluated cached result; a single `set \"$FILE\" / --prop recalcFields=seq` (see §SEQ numbering below) fills the real numbers in document order — no per-field patching.\n\n### SEQ auto-numbering — figures and tables\n\nA SEQ field is a counter with a name (`identifier`). Every `SEQ Figure` increments the Figure counter on **recalc**; every `SEQ Table` increments the Table counter.\n\n**SEQ cached values — one command, after all captions are added.** A freshly-added SEQ field has no evaluated cached number; `view text` shows the `#OCLI_NOTEVAL!{SEQ Figure}` sentinel (and `Format[\"evaluated\"]=false`) until you recalc. **Do NOT patch each field by hand.** Once every figure/table caption is in place, run once:\n```bash\nofficecli set \"$FILE\" / --prop recalcFields=seq\n```\nIt counts `SEQ Figure` / `SEQ Table` fields in body document order and writes the real cached values (`Figure 1 / Figure 2 / Figure 3`), flipping `evaluated` true. Re-run it after inserting a caption mid-document so the numbers stay in sync. (Heading-relative `\\s` and SEQ inside headers/footers defer to Word's own recompute — see `help docx document`.) Verify: `officecli view \"$FILE\" text` shows distinct ascending numbers.\n\n```bash\n# Figure with caption BELOW the image. Caption = \"Figure <seq>: title\" + optional bookmark for cross-ref.\nofficecli add \"$FILE\" /body --type picture --prop src=arch.png --prop width=5in\nofficecli set \"$FILE\" \"/body/p[last()]/r[last()]\" --prop alt=\"Model architecture: attention over time-series sensors\"\n# Caption paragraph (below the figure, per academic convention)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Figure \" --prop style=Caption --prop size=10pt --prop italic=true --prop align=center\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=seq --prop identifier=Figure\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\": Attention-based anomaly detection model.\"\n# Bookmark the caption so other paragraphs can PAGEREF it\nofficecli add \"$FILE\" /body --type bookmark --prop name=fig_arch\n# (Numbers are filled later by a single `set / --prop recalcFields=seq`, after all captions exist.)\n```\n\n### PAGEREF — cross-reference by bookmark\n\n```bash\n# Cross-ref paragraph: \"see Figure 1 on page X\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"As shown in Figure 1 (see page \" --prop size=11pt --prop lineSpacing=1.5x\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=pageref --prop name=fig_arch\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\").\"\n```\n\n### Tables — caption ABOVE\n\n```bash\n# Caption first (ABOVE the table), THEN the table\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Table \" --prop style=Caption --prop size=10pt --prop italic=true --prop spaceAfter=6pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type field --prop fieldType=seq --prop identifier=Table\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\": Participant demographics (N=47).\"\nofficecli add \"$FILE\" /body --type table --prop rows=5 --prop cols=4 --prop width=100%\n# ... fill header + rows per docx v2 §Tables\n```\n\n### Verify SEQ + PAGEREF fields landed\n\n```bash\n# At least one SEQ Figure or SEQ Table in the body document part\nofficecli raw \"$FILE\" /document | grep -c 'w:instrText[^>]*>[^<]*SEQ'   # expect ≥ 1\nofficecli raw \"$FILE\" /document | grep -c 'w:instrText[^>]*>[^<]*PAGEREF' # 0 ok if no cross-refs\n```\n\nLive fields carry **cached values** that render stale until a human presses F9 in Word. Expect \"Figure 1\" to show as `1`, `2`, ... immediately after recalc; before recalc, some viewers render `0` or blank. Judge field presence by `fldChar` existence, not by visible digit (→ see docx v2 §Field / cached-value spot-check).\n\n## Footnotes vs endnotes\n\n**Footnote** — sits at the bottom of the page where its anchor paragraph lives. Used for source citations in Chicago Notes-Bib, content asides in any style.\n\n**Endnote** — sits at the end of the document (or before the bibliography). Used by some venues in place of footnotes, or for long contextual notes that would clutter the page.\n\n```bash\n# Footnote anchored to paragraph N\nofficecli add \"$FILE\" \"/body/p[3]\" --type footnote --prop text=\"Smith et al. reported similar findings in their 2023 review.\"\n# Endnote — anchored to paragraph N (like footnote); lands at /endnote[@endnoteId=N]\nofficecli add \"$FILE\" \"/body/p[3]\" --type endnote --prop text=\"Extended derivation of equation (4) is available at the project repository.\"\n```\n\nBoth appear as empty-string runs in `view annotated` output (`r[N] \"\"`) — the run carries a `<w:footnoteReference>` XML element, not visible text. Confirm insertion with `officecli query \"$FILE\" 'footnote'` or `officecli get \"$FILE\" \"/footnotes/footnote[N]\"`. Footnotes do NOT shift paragraph indices; add them in any order after body content is in place. Full schema: `officecli help docx footnote` / `officecli help docx endnote`.\n\n## Bibliography section\n\nEvery academic paper ends with a reference list. The name of the section depends on the style (**References** for APA / IEEE / Chicago Author-Date; **Bibliography** for Chicago Notes-Bib; **Works Cited** for MLA). Each entry is a separate paragraph with **hanging indent**.\n\n```bash\n# Section heading — same as body Heading1 (excluded from body numbering by convention)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"References\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=12pt\n# Each entry: hanging indent 720 twips (0.5\"), with indent=720 as the partner (first line flush, wraps indented)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Smith, J. (2024). Remote work and cohesion. Journal of Applied Psychology, 109(3), 412-430.\" --prop size=12pt --prop lineSpacing=2x --prop indent=720 --prop hangingIndent=720\n# DOI hyperlink on its own run appended to the entry paragraph\nofficecli add \"$FILE\" \"/body/p[last()]\" --type hyperlink --prop url=\"https://doi.org/10.1037/apl0001123\" --prop text=\"https://doi.org/10.1037/apl0001123\"\n```\n\nVerified: `--prop indent=720 --prop hangingIndent=720` is the canonical hanging-indent pair per `officecli help docx paragraph`. The old `ind.firstLine=-720` form (negative first-line indent) is NOT canonical and fails schema on emit — → see docx v2 §Schema-invalid-on-emit.\n\n**Round-trip QA.** Count in-text citation markers (APA `(Author, Year)`, IEEE `[N]`, MLA `(Author N)`) vs reference-list entries. See Delivery Gate 4 below. Every cited key must resolve; every listed entry should be cited at least once.\n\n## Multi-column (IEEE journal two-column recipe)\n\nIEEE and many engineering / physics journals render body text in two columns with a single-column abstract above. The mechanism: a section break with `type=continuous` and `columns=2`, then another section break at the end to **revert** to single-column.\n\n**The reversion step is not optional.** Without it, the rest of the document — including references — renders as two columns. This is the single most common multi-column failure.\n\n```bash\nFILE=\"ieee.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\n# 1. Title, authors, affiliation — single-column (the default first section)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Attention-Based Anomaly Detection for Industrial Time Series\" --prop align=center --prop size=18pt --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen, Bob Martinez\" --prop align=center --prop size=11pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Department of CS, Stanford University\" --prop align=center --prop size=10pt --prop spaceAfter=18pt\n\n# 2. Abstract — still single-column, block-style\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Abstract\" --prop align=center --prop size=12pt --prop bold=true --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"We present an attention-based model for detecting anomalies in industrial sensor time series...\" --prop size=10pt --prop lineSpacing=1.15x --prop spaceAfter=12pt\n\n# 3. Section break + two-column from here on\n#    `/section[last()]` resolves to the final section (like p[last()]); an explicit /section[N] also works.\nofficecli add \"$FILE\" /body --type section --prop type=continuous\nSECTION_COUNT=$(officecli query \"$FILE\" section --json | jq '.data.results | length')\n# After the add, SECTION_COUNT should be 2 — [1] is pre-break, [2] is post-break (2-col body area).\nofficecli set \"$FILE\" \"/section[2]\" --prop columns=2 --prop columnSpace=1cm\n\n# 4. Body — IEEE wants Roman numerals + ALL CAPS section titles (P1.2).\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"I. INTRODUCTION\" --prop style=Heading1 --prop size=10pt --prop bold=true\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Industrial anomaly detection has been studied since [1]...\" --prop size=10pt --prop lineSpacing=1.15x --prop firstLineIndent=360\n\n# 5. At the end of 2-column body, ANOTHER section break + revert to single column for references / appendices\n# (If you want references in 2-col too, skip step 5 — but most IEEE papers use 2-col for references as well.)\n# officecli add \"$FILE\" /body --type section --prop type=continuous\n# Use /section[last()] for the final section, or re-count for an explicit /section[N]:\n# officecli set \"$FILE\" \"/section[3]\" --prop columns=1\n\n# 6. Footer, close, validate\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=9pt --prop field=page\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\n**Visual verify.** Run `officecli view \"$FILE\" html` and Read the returned HTML to audit the rendered output. The abstract must render as full-width and the introduction onward as two columns. If the abstract wraps into two narrow columns, the first section break landed before the abstract — move it.\n\n**Section index bookkeeping.** Each `add /body --type section` inserts one empty paragraph into `/body` (the section-break marker). All subsequent `p[N]` indices shift by +1 per section break. Plan section breaks in advance; after adding a break, `officecli get \"$FILE\" /body --depth 1` to re-index before continuing.\n\nFull section schema (`columns`, `columnSpace`, `orientation`, `pageNumFmt`, `titlePage`, `lineNumbers`): `officecli help docx section`.\n\n## Abstract / keywords / affiliation block\n\nFirst-page metadata stack: title (centered 20-22pt bold) → authors (centered 12pt, superscript `^1 ^2` for multi-affiliation) → affiliations (centered 11pt, keyed to superscripts) → submission target / date → **Abstract** heading (14pt bold) → abstract body (block-style, **NO `firstLineIndent`**, 150-300 words) → keywords line (italic 11pt). Same \"cover ≥ 60% filled\" rule as docx v2.\n\n```bash\n# Superscript affiliation markers (multi-institution paper)\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Alice Chen\" --prop align=center --prop size=12pt\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\"1\" --prop superscript=true\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\", Bob Martinez\"\nofficecli add \"$FILE\" \"/body/p[last()]\" --type run --prop text=\"2\" --prop superscript=true\n# Running header (skip on cover via type=first empty header — see docx v2 §headers)\nofficecli add \"$FILE\" / --type header --prop type=default --prop align=right --prop size=9pt --prop text=\"Short Running Title\"\n```\n\n**Nature-family 2-col abstract** is rare — if required, open a `section type=continuous columns=2` BEFORE the abstract heading; short abstracts (<100 words) leave ragged columns. **Mirrored odd/even headers** are exposed by the high-level API: `officecli set \"$FILE\" /settings --prop evenAndOddHeaders=true`, then add the even header with `--type header --prop type=even`. Full header schema: `officecli help docx header`.\n\n## QA — Delivery Gate (executable)\n\n**Assume there are problems. Your job is to find them.** First render is almost never correct. Run this block before declaring done.\n\n### Gates 1-3 — inherited from docx v2\n\n→ see docx v2 §Delivery Gate. Schema validate, token leak grep, live PAGE field structure. Copy-paste the docx v2 gate block first. Every check must print its success message.\n\n### Gate 4 — citation round-trip\n\nEvery in-text citation key should resolve to a bibliography entry. Count mismatches = REJECT.\n\n```bash\n# IEEE example (bracketed numerics). Adjust regex for APA (Author, Year) or MLA (Author Page).\nCITATIONS=$(officecli view \"$FILE\" text | grep -oE '\\[[0-9]+\\]' | sort -u | wc -l)\nENTRIES=$(officecli query \"$FILE\" 'paragraph[hangingIndent]' --json | jq '.data.results | length')\necho \"In-text citation markers: $CITATIONS | Bibliography entries: $ENTRIES\"\n# REJECT when citations exceed entries (cites without references). Entries > citations is allowed by some venues.\n[ \"$CITATIONS\" -le \"$ENTRIES\" ] && echo \"Gate 4 OK\" || { echo \"REJECT Gate 4: $CITATIONS in-text markers but only $ENTRIES bibliography entries\"; exit 1; }\n```\n\n### Gate 5a — SEQ presence + cached numbers distinct\n\nIf the paper has any numbered figure or table, the body must carry live `SEQ` fields AND their cached values must show distinct ascending numbers (else `view text` and downstream viewers that don't recompute cached fields will show \"Figure 1\" for all).\n\n```bash\n# Count SEQ fields via query (raw-grep collapses multi-matches on one XML line → undercounts).\nSEQ_COUNT=$(officecli query \"$FILE\" 'field[fieldType=seq]' --json | jq '.data.results | length')\nVISIBLE_FIG=$(officecli view \"$FILE\" text | grep -cE '(Figure|Table) [0-9]+')\nif [ \"$VISIBLE_FIG\" -gt 0 ] && [ \"$SEQ_COUNT\" -eq 0 ]; then\n  echo \"REJECT Gate 5a: $VISIBLE_FIG visible Figure/Table labels but 0 SEQ fields.\"\n  exit 1\nfi\n# Cached values must be distinct. Run `set / --prop recalcFields=seq` once after all captions exist;\n# before recalc the fields render the #OCLI_NOTEVAL! sentinel, after recalc Figure 1 / Figure 2 / Figure 3:\nDISTINCT=$(officecli view \"$FILE\" text | grep -oE '(Figure|Table) [0-9]+' | sort -u | wc -l)\n[ \"$SEQ_COUNT\" -le \"$DISTINCT\" ] && echo \"Gate 5a OK (SEQ=$SEQ_COUNT, distinct=$DISTINCT)\" || { echo \"REJECT Gate 5a: $SEQ_COUNT SEQ fields but only $DISTINCT distinct rendered labels — run 'set \\\"$FILE\\\" / --prop recalcFields=seq'\"; exit 1; }\n```\n\n### Gate 5b — Visual audit via HTML preview (MANDATORY, not optional)\n\nGates 1–5a catch schema, token leaks, live-field presence, citation counts. **They do NOT catch physical assembly defects** — scrambled page order, a duplicated Abstract mid-document, three figures all labeled \"Fig. 1\" despite SEQ field presence, equation variables rendering as plain-text LaTeX (`lambda_1`, `x_{t+1}`) instead of math. Do not skip — Gates 1–5a pass ≠ visual OK.\n\nRun `officecli view \"$FILE\" html` and Read the returned HTML path. For every page of the paper, answer:\n\n> (a) Are pages in logical academic sequence? (Title → Abstract → Keywords → Introduction → body → References — no forward jumps, no backward leaks.)\n> (b) Does the Abstract appear exactly once, not duplicated mid-document?\n> (c) Are Figure N / Table N labels distinct and ascending? (Fig. 1, Fig. 2, Fig. 3 — not all \"Fig. 1\". Same for tables.)\n> (d) Do equations render as math? (Italicized variables, Greek letters like λ / α, proper integrals / fractions — NOT plain-text `lambda_1`, `x_{t+1}`, `\\int`.)\n> (e) For IEEE papers: are section titles ALL CAPS with Roman numerals (`I. INTRODUCTION`)? Are tables Roman (`Table I`, `Table II`)?\n> (f) For APA papers: are Level-1 headings centered bold and unnumbered (not `1. Introduction`)?\n> (g) Does every in-text \"see Fig. N\" / \"see Table N\" resolve to a figure/table that actually carries that number?\n> (h) Heading hierarchy visually distinct (size + weight) across H1 / H2 / H3?\n\nReport every instance. If even one defect is present → REJECT; do not deliver until fixed.\n\n**Human preview (optional).** If you want the user to visually preview the paper, run `officecli watch \"$FILE\"` for a live preview the user can open at their own discretion, or have them open the `.docx` directly in Word / WPS / Pages. For final visual verification, open the file in the target viewer.\n\n### Honest limit\n\n`validate` catches schema errors, not academic-style errors. A document passes `validate` with APA citations in an IEEE paper, footnotes in a style that forbids them, or figures with hardcoded numbers that drift when a new figure is inserted. The gates above — especially Gate 4 (round-trip) and Gate 5 (SEQ presence) — are how you catch what validate cannot.\n\n## Known Issues & Pitfalls (academic-specific)\n\n→ Base pitfalls (shell escape, `\\$ \\t \\n` literals, table cell formatting order, page-break boundaries, `shd.fill` / `ind.firstLine` schema-invalid forms, TOC cached values, watermark two-step): see docx v2 §Known Issues & Pitfalls.\n\nAcademic-specific:\n\n- **`\\left(...\\right)` / `\\left[...\\right]` with a sub/superscript INSIDE the delimiters → parse error** (`cast object … Subscript`). An outer script (`\\left(x+y\\right)^2`) is fine. Use plain `(`, `)`, `[`, `]` — OMML auto-sizes in display mode.\n- **`move` on `/body/oMathPara[N]` reorders the equation** (the wrapping paragraph moves). `--before <path>` may leave a stray empty paragraph; prefer `--index` / `--after`.\n- **Section break +1 paragraph offset.** Each `add /body --type section` inserts one empty paragraph into `/body`. All `p[N]` indices after the break shift by +1. Plan breaks; after any `add section`, `officecli get \"$FILE\" /body --depth 1` to re-index.\n- **`/section[last()]` resolves to the final section** (mirrors `p[last()]`), for both get and set. An explicit `/section[N]` also works:\n  ```bash\n  SECTION_COUNT=$(officecli query \"$FILE\" section --json | jq '.data.results | length')\n  # use /section[last()] for the final section, or an explicit /section[2], /section[3], ...\n  ```\n  Each `add /body --type section` increments the count. Re-query after every break.\n- **Multi-column does NOT auto-revert.** After a `columns=2` section, you must add another section break and explicitly set `columns=1` on the new `/section[N]` (N = post-revert count) — otherwise the rest of the document, including references, renders as two columns. Verify with `officecli get \"$FILE\" \"/section[N]\"` for each N.\n- **`--type equation` on a `tc[N]` cell path is rejected with a guard error** (`table cells only accept paragraphs, tables, or SDTs`) — no bad XML is written. Inside a table cell, target `tc[N]/p[1]` with `--prop mode=inline` instead.\n- **SEQ caption numbers are filled by `set / --prop recalcFields=seq`** (run once after all captions exist), not by per-field raw-set patching. A pre-recalc SEQ field shows the `#OCLI_NOTEVAL!` sentinel in `view text`.\n- **Hanging-indent canonical form is `indent=720 hangingIndent=720`.** Not `ind.firstLine=-720`. The dotted form emits `<w:ind>` after `<w:jc>` and fails schema on emit.\n- **Footnote reference runs show as empty strings in `view annotated`.** The `<w:footnoteReference>` XML element has no visible text on the reference side; the note body lives in `/footnotes/footnote[N]`. Confirm with `officecli query \"$FILE\" 'footnote'`, not by eyeballing `view text`.\n- **Caption placement:** Table caption ABOVE the table; Figure caption BELOW the figure. Every major style (APA, Chicago, IEEE, MLA) agrees. Putting a Table caption below the table is an academic-style error, not a rendering issue — `validate` will not catch it.\n- **TOC cached rendering / shell-escape:** → see docx v2 §Table of Contents, §Shell escape.\n\n## Renderer quirks (cross-viewer)\n\n→ see docx v2 §Renderer quirks. PAGE / TOC cached values, OMML baseline shifts, scheme colors — all identical quirks apply to academic papers. Before calling an equation or a citation marker broken, open the file in the user's target viewer (Word, WPS, Pages) — if it renders correctly there, it is a viewer quirk, not a skill defect.\n\n## Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <element> --json`. Help is the authoritative schema; this skill is the decision guide for academic deltas on top of docx v2.\n","frontmatter":{"name":"officecli-academic-paper","description":"Use this skill to build academic-style .docx output: journal / conference / thesis chapters carrying formal citation style (APA, Chicago, IEEE, MLA), numbered equations, figure & table cross-references, footnotes/endnotes, bibliography, or multi-column journal layout. Trigger on: 'research paper', 'journal paper', 'conference paper', 'manuscript', 'thesis', 'APA', 'MLA', 'Chicago', 'IEEE two-column', 'bibliography', 'hanging indent', 'citation style', 'abstract + keywords', 'equation numbering', 'cross-reference', paper with footnotes/endnotes. Output is a single .docx."},"isInternal":false,"tokens":11836,"sizeBytes":45705},{"name":"SKILL.md","path":"skills/officecli-data-dashboard/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-data-dashboard/SKILL.md","title":"officecli-data-dashboard","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-data-dashboard\ndescription: \"Use this skill to build a multi-element Excel dashboard — Dashboard sheet on open, multiple formula-driven KPI cards, multiple charts, sparklines, and conditional formatting — from CSV or tabular input. Trigger on: 'dashboard', 'KPI dashboard', 'analytics dashboard', 'executive dashboard', 'metrics dashboard', 'CSV to dashboard', 'data visualization'. Output is a single .xlsx. Scene-layer on officecli-xlsx: inherits every xlsx hard rule. DO NOT invoke for: a single budget tracker / one-sheet CSV-with-formatting (use xlsx), a 3-statement / DCF / LBO financial model (use financial-model), a weekly report with at most 1 chart and fewer than 10 rows (use xlsx).\"\n---\n\n# Data Dashboard (scene-layer on officecli-xlsx)\n\nA dashboard is not \"a spreadsheet with charts\". It is a composition: **one Dashboard sheet the user lands on** with formula-driven KPI cards, cell-range-linked charts, sparklines, and semantic conditional formatting. Everything else (raw data, aggregations) is upstream infrastructure the user should never need to open. This skill teaches the composition pattern. Everything about the xlsx engine — cells, formulas, batch JSON, shell quoting, validate, HTML preview — comes from `officecli-xlsx` and is not re-taught here.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**When a prop name, enum value, or alias is uncertain, consult help before guessing.**\n\n```bash\nofficecli help xlsx                          # element list\nofficecli help xlsx chart                    # full schema for charts\nofficecli help xlsx sparkline                # sparklines\nofficecli help xlsx conditionalformatting    # all CF rule types\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help wins**. DeferredAddKeys (`combosplit`, `holesize`) work on `add` only — see Reference.\n\n## Mental Model & Inheritance\n\nThis skill **inherits every xlsx hard rule** from `officecli-xlsx` — shell quoting, zero formula errors, visual delivery floor, batch JSON shape (`{\"command\":\"set\"|\"add\",\"path\":...,\"props\":{...}}` — key is `command`, NOT `action`), batch JSON dotted-name rule, chart data-feed forms, batch+resident limits, `validate` discipline. Read officecli-xlsx first; honour those rules, do not re-teach them here.\n\n**Reverse handoff — do NOT use this skill when:**\n\n- The ask is a **single-sheet CSV-with-formatting tracker** (no Dashboard sheet, no KPI cards, ≤ 1 chart) → go back to `officecli-xlsx`.\n- The ask is a **3-statement / DCF / LBO financial model** with blue-inputs / black-formulas / cross-sheet drivers → use `officecli-financial-model`.\n- The ask is a **weekly status report** with one SUMIF summary and one chart over < 10 rows → `officecli-xlsx`.\n\nThis skill only accepts: \"a Dashboard sheet the user opens first, multiple KPI cards, multiple charts, some CF / sparklines\".\n\n## Shell & Execution Discipline\n\n→ see officecli-xlsx §Shell & Execution Discipline for the baseline (quoting, heredoc for `!`, incremental execution).\n\nTwo increments specific to dashboards:\n\n- **Long chart `add` commands exceed 180 chars.** Always split across lines with trailing `\\`; never pack a chart command onto a single line. The longer the command, the higher the chance a shell-escape bug hides inside it.\n- **Multi-instance counts use `query --json | jq length`, never `raw-get | grep -c`.** Example: `officecli query \"$FILE\" chart --json | jq '.data.results | length'` for \"how many charts do I have?\".\n\n## Core Principles\n\nFive non-negotiable principles. If any one is violated the output is not a dashboard, it is a spreadsheet that happens to have a chart.\n\n1. **Formula-driven KPIs.** Every KPI value on the Dashboard sheet is a formula — `SUM`, `AVERAGE`, `IFERROR((...-...)/...,0)`, whatever — referring to cells on the Data / Summary sheet. Never hardcode a computed number. When the underlying data changes tomorrow, KPIs update on open.\n\n2. **Cell-range references for charts.** Every chart series reads from a cell range: `series1.values=\"Sheet1!B2:B13\"`. Inline `data=\"Revenue:100,200,300\"` is for a 5-minute demo, not a delivered dashboard. The one exception: data requires an aggregation Excel cannot express (rare) — document the exception in a comment cell.\n\n3. **Dashboard-first architecture.** KPI label cells, KPI value cells, charts, sparklines all live on the **Dashboard** sheet — the single sheet a user lands on. Raw imports and `SUMIFS` rollups live on Data / Summary sheets, upstream of the Dashboard. The user should never need to switch tabs to find the answer.\n\n4. **Visible cells only for chart sources.** LibreOffice does not evaluate formulas in hidden columns or hidden sheets at render time. A chart whose `series1.values` points at a hidden-column `SUMIFS` renders blank. Pattern: aggregate into a **visible** Summary sheet, point charts at Summary cells, hide only helper columns that are not chart sources.\n\n5. **Data-size-aware complexity.** A 10-row dataset does not get 5 KPIs and 4 charts. A 200-row dataset does not get 1 KPI and 1 chart. Scale up the composition with the input (table in §Design Ideas). Overbuilding is as wrong as underbuilding.\n\n## Requirements\n\nAll `officecli-xlsx` requirements apply (→ see officecli-xlsx §Requirements for Outputs). Dashboards add these:\n\n- **Dashboard sheet is the active tab on open.** Confirm 0-based sheet index with `officecli query \"$FILE\" sheet` BEFORE filling `activeTab=\"N\"`. Never guess the index.\n- **`calc.fullCalcOnLoad=true`.** Set via `officecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true`. Do NOT `raw-set` `<calcPr>` — it produces duplicate elements that fail validate.\n- **Refresh downstream cachedValue after every upstream edit.** `fullCalcOnLoad=true` schedules runtime recalc only; it does NOT refresh build-time `cachedValue`. After `set B=100 → set E==B+D → fix B=150`, E is stale until you re-issue E's formula (or close/reopen). Stale cache ships \"Net Change = 0\" to the board.\n- **Every chart has a descriptive title and every series has a name.** `\"Series1\"` in a legend is unfinished work.\n- **Every KPI value cell has a formula.** Verifiable: `officecli query \"$FILE\" 'Dashboard!:has(formula)' --json | jq '.data.results | length'` should equal your planned KPI count.\n- **Header row fill on every data sheet.** Data sheet, Summary sheet, and any secondary data sheet need row 1 filled (e.g., `fill=1F3864 + font.color=FFFFFF + font.bold=true`).\n- **10+ rows on Data sheet → ≥ 1 CF rule on a numeric column.** A 20-row table with zero visual scanning aid is a quality miss.\n- **Dashboard value columns sized to the widest expected cachedValue — not a fixed 22.** Rule of thumb at 24pt bold + currency numFmt: `width ≈ ceil((visible_chars + 2) × 1.3)`. A KPI holding `¥1,958,414,250` (14 visible chars with currency + commas) needs `width ≥ 28`; a 4-digit KPI still needs `width ≥ 22` as the floor. Hardcoding `22` for a 10+ digit KPI is how `###` ships to the user.\n- **Sparkline row height ≥ 20.** A sparkline in a default 15pt row is a flat squiggle — set `/Dashboard/row[N] height=22` (or 24 when paired with a 24pt KPI value cell in the same row).\n- **Print deliverables set `_xlnm.Print_Area` scoped to Dashboard** + hide non-Dashboard sheets + add `<pageSetup fitToPage/>`. Without all three, the print pipeline emits every sheet and Dashboard lands on page 2+. See §Print-ready delivery for the exact commands.\n\n## Quick Start\n\nMinimal viable dashboard: 12-month revenue CSV → 4 KPIs + 1 line chart + activeTab + fullCalcOnLoad. Adapt the numbers, don't copy-paste blind. Broken into phases so a single failed phase is obvious.\n\n**Phase 1 — Data sheet: create, import, format.**\n\n```bash\nFILE=my_dashboard.xlsx\nofficecli create \"$FILE\"\nofficecli import \"$FILE\" /Sheet1 --file sales.csv --header\nofficecli set \"$FILE\" '/Sheet1/col[A]' --prop width=12\nofficecli set \"$FILE\" '/Sheet1/col[B]' --prop width=15\nofficecli set \"$FILE\" '/Sheet1/B2:B13' --prop numFmt='$#,##0'\nofficecli set \"$FILE\" '/Sheet1/A1:B1' --prop fill=1F3864 --prop font.color=FFFFFF --prop font.bold=true\n```\n\n**Phase 2 — Dashboard sheet + one KPI card.**\n\n```bash\nofficecli add \"$FILE\" / --type sheet --prop name=Dashboard\nofficecli set \"$FILE\" '/Dashboard/col[A]' --prop width=22\nofficecli set \"$FILE\" '/Dashboard/col[B]' --prop width=12\nofficecli set \"$FILE\" /Dashboard/A1 --prop value=\"Total Revenue\" --prop font.size=9 --prop font.color=666666 --prop bold=true\nofficecli set \"$FILE\" /Dashboard/A2 --prop 'formula==SUM(Sheet1!B2:B13)' --prop numFmt='$#,##0' --prop font.size=24 --prop bold=true --prop font.color=2E7D32\n```\n\n**Phase 3 — Sparkline + chart.**\n\n```bash\nofficecli add \"$FILE\" /Dashboard --type sparkline --prop cell=B2 --prop range='Sheet1!B2:B13' --prop type=line --prop color=4472C4 --prop highPoint=true --prop highMarkerColor=FF0000\nofficecli add \"$FILE\" /Dashboard --type chart \\\n  --prop chartType=line \\\n  --prop title=\"Revenue Trend\" \\\n  --prop series1.name=\"Revenue\" \\\n  --prop series1.values='Sheet1!B2:B13' \\\n  --prop series1.categories='Sheet1!A2:A13' \\\n  --prop preset=dashboard --prop axisNumFmt='$#,##0' \\\n  --prop x=0 --prop y=5 --prop width=10 --prop height=15\n```\n\n**Phase 4 — fullCalcOnLoad → activeTab (LAST) → close → validate.**\n\n```bash\nofficecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true\n\n# Resolve Dashboard's 0-based index from the actual sheet list — never hardcode.\nDASH_IDX=$(officecli query \"$FILE\" sheet --json \\\n  | jq '[.data.results[].path] | index(\"/Dashboard\")')\nofficecli raw-set \"$FILE\" /workbook --xpath \"//x:sheets\" --action insertbefore \\\n  --xml \"<bookViews xmlns=\\\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\\\"><workbookView activeTab=\\\"$DASH_IDX\\\" /></bookViews>\"\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified end-to-end on a 12-row revenue CSV: `validate` reports no errors, Dashboard opens first, `Dashboard/A2.cachedValue` resolves (2,075,000 for the test data), chart renders with values linked.\n\n## Design Ideas\n\nOptions, not templates. The user's data and audience drive the choices.\n\n### Layout patterns (pick one, stay consistent)\n\n**Pattern 1 — executive summary** (board packs): KPI strip A1:H4, charts stack from row 6.\n```\n┌ KPI1 │ KPI2 │ KPI3 │ KPI4 ┐  rows 1-4\n├──────┴──────┴──────┴──────┤\n│     Chart 1 (wide)        │  rows 6-18\n├───────────────┬───────────┤\n│   Chart 2     │  Chart 3  │  rows 20-32\n```\n\n**Pattern 2 — ops console** (live ops): KPIs down A:B, charts fill C:L.\n```\n│ KPI1 │                   │\n│ KPI2 │    Chart 1        │  rows 1-12\n│ KPI3 │                   │\n│ KPI4 ├───────────────────┤\n│ KPI5 │    Chart 2        │  rows 14-26\n```\n\n**Pattern 3 — scorecard** (≥ 6 KPIs, no dominant chart): grid of 2×3 cards (label / value / sparkline).\n```\n│ KPI1 │ KPI2 │ KPI3 │  rows 1-4\n│ KPI4 │ KPI5 │ KPI6 │  rows 5-8\n```\n\n### Complexity scaling by data size\n\n| Rows | KPIs | Charts | Sparklines | CF rules | Preset |\n|---|---|---|---|---|---|\n| < 10 | 1–2 | 1 | skip | 0–1 | `minimal` |\n| 10–50 | 2–3 | 2 | only if sequential time-series | 1–2 | `dashboard` |\n| 50–200 | 3–5 | 2–3 | only if sequential time-series | 2–3 | `dashboard` |\n| 200+ | 3–5 | 3 | only if sequential time-series | 3–4 | `dashboard` |\n\n### Chart type selection\n\n| Data pattern | Chart type | Notes |\n|---|---|---|\n| Trend over time, one series | `line` | Add `trendline=linear` to show direction on noisy series |\n| Trend over time, multiple components | `line` (multi-series) or `columnStacked` | Stacked when components sum to a meaningful total |\n| Comparison across categories in time order | `column` | Not `bar` — horizontal bars break left-to-right time reading |\n| Part-of-whole breakdown | `doughnut` | Prefer over `pie`: `chartType=pie` has a known LibreOffice blank-render regression |\n| Budget vs actual | `combo` with `combosplit=1` | First series as bars, rest as lines |\n| Correlation | `scatter` | X-axis via `categories` / `series1.categories` — `series1.xValues` is UNSUPPORTED |\n\n### Preset options\n\n`--prop preset=<name>` on every chart. Options: `minimal`, `dashboard`, `corporate`, `magazine`, `colorful`, `monochrome`, `dark`. Pick one and stay consistent across all charts on a single Dashboard — mixing presets reads as accidental.\n\n### Conditional formatting — semantic colors\n\nFour CF rule types; each uses `--type <shorthand>` at `add` time:\n\n| Intent | `--type` | Typical props |\n|---|---|---|\n| Magnitude bar (sales, spend) | `databar` | `sqref=B2:B13 color=4472C4` — explicit `min=0 max=<plausible>` recommended for predictable scaling, but omitting them is valid (defaults to data min/max) |\n| Heat map (rates, growth) | `colorscale` | `sqref=D2:D13 mincolor=FFCDD2 midcolor=FFFFFF maxcolor=C8E6C9` |\n| Status indicator | `iconset` | `sqref=E2:E13 iconset=3Arrows` — see help for the full enum |\n| Custom business rule | `formulacf` | `sqref=B2:B13 'formula=$B2>=100000' fill=C8E6C9 font.color=2E7D32` — `font.bold` works on CF too |\n\nSemantic colors to stay consistent within a dashboard:\n\n- good / positive: fill `C8E6C9`, font `2E7D32`\n- bad / negative: fill `FFCDD2`, font `C62828`\n- neutral: fill `F5F5F5`, font `666666`\n\n### KPI card anatomy\n\nA card is a label cell + a value cell. The label is small gray (font.size=9, font.color=666666, bold); the value is large bold (font.size=24, bold=true, numFmt, font.color signals tone). One row of light fill (e.g. `F0F4FF`) across the card area gives the \"card\" read without building merged-cell scaffolds. Value column width must be sized to the largest cachedValue — never narrower than 22, often 26–32 for 8+ digit currency (see Requirements).\n\n### Chart width budget by title length\n\nAt the `dashboard` preset's default title font, the chart plot-box width (in column units) must stay ahead of the title string, or the title clips mid-word. Rule of thumb: `chart.width ≥ ceil(title.length × 0.18)`. A 35-character title (\"Department: Year-End Headcount vs Attrition Rate\") needs `width ≥ 7`; be safer and use 10–12. If the anchor cannot be widened, shorten the title to ≤ 25 characters — clipped titles in a board-ready deliverable are indefensible.\n\n`officecli get chart[N]` exposes numeric `width` (e.g. `width=480pt`) alongside `anchor` (e.g. `\"A6:K21\"`) at `.data.results[0].format`. Either is usable for Gate 2 — derive column span from anchor letters (A→K = 10 cols) when you need a column-unit budget.\n\n### Print-ready delivery (board-pack / investor-send / one-pager)\n\nTriggers: ask contains \"print\" / \"一页\" / \"董事会\" / \"投资人\". Four artefacts on the Dashboard sheet; non-Dashboard sheets hidden so the print pipeline emits one page only.\n\n```bash\n# 1. Print_Area scoped to Dashboard (xlnm convention).\nofficecli add \"$FILE\" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Dashboard --prop 'refersTo=Dashboard!$A$1:$H$36'\n# 2. fit-to-page on Dashboard.\nofficecli raw-set \"$FILE\" /Dashboard --xpath \"//x:worksheet\" --action prepend --xml '<sheetPr xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><pageSetUpPr fitToPage=\"1\"/></sheetPr>'\n# 3. Landscape page setup.\nofficecli raw-set \"$FILE\" /Dashboard --xpath \"//x:sheetData\" --action insertafter --xml '<pageSetup orientation=\"landscape\" paperSize=\"9\" fitToWidth=\"1\" fitToHeight=\"1\"/>'\n# 4. Hide non-Dashboard sheets — Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.\nfor S in Sheet1 Summary; do\n  officecli raw-set \"$FILE\" /workbook --xpath \"//x:sheet[@name='$S']\" --action setattr --xml \"state=hidden\" || true\ndone\n```\n\nDelete any `Print_Area` set on Data / Summary sheets — conflicting scopes emit multi-page output.\n\n## QA (REQUIRED — Delivery Gate)\n\n**Assume there are problems. Your job is to find them.** A chart that was rendered does not mean a chart that was meaningful. \"validate pass\" is not delivery; \"the Dashboard sheet reads like someone who knows the business made it\" is delivery.\n\n### Minimum cycle before \"done\"\n\nInherit the xlsx baseline (`view issues`, formula error queries, `validate`, HTML preview scan): → see officecli-xlsx §QA minimum cycle.\n\nThen run the dashboard-specific Delivery Gates. Each gate uses **COUNT-then-if** pattern with a `.data.*` wrapper — never chain `&& echo OK || echo FAIL`.\n\n**Gate 1 — KPI formula coverage.** Every planned KPI cell must carry a formula. Adjust `-lt 2` to your plan (4 KPIs → `-lt 4`).\n\n```bash\nKPI_FORMULAS=$(officecli query \"$FILE\" 'Dashboard!:has(formula)' --json | jq '.data.results | length')\n[ \"$KPI_FORMULAS\" -lt 2 ] && { echo \"REJECT Gate 1: $KPI_FORMULAS formula cells on Dashboard\"; exit 1; }\n```\n\n**Gate 2 — Chart count matches plan, every chart has data + plausible title width.**\n\n```bash\nCHART_COUNT=$(officecli query \"$FILE\" chart --json | jq '.data.results | length')\n[ \"$CHART_COUNT\" -lt 1 ] && { echo \"REJECT Gate 2: zero charts\"; exit 1; }\ncol_num () { local c=$1 n=0; for ((k=0;k<${#c};k++)); do n=$((n*26+$(printf '%d' \"'${c:$k:1}\")-64)); done; echo \"$n\"; }\nfor i in $(seq 1 \"$CHART_COUNT\"); do\n  JSON=$(officecli get \"$FILE\" \"/Dashboard/chart[$i]\" --json)\n  SC=$(echo \"$JSON\" | jq -r '.data.results[0].format.seriesCount // 0')\n  TITLE=$(echo \"$JSON\" | jq -r '.data.results[0].format.title // \"\"')\n  ANCHOR=$(echo \"$JSON\" | jq -r '.data.results[0].format.anchor // \"\"')\n  [ \"$SC\" = \"0\" ] || [ -z \"$TITLE\" ] && { echo \"REJECT Gate 2: chart[$i] seriesCount=$SC title='$TITLE'\"; exit 1; }\n  [ -z \"$ANCHOR\" ] && continue\n  LCOL=$(echo \"${ANCHOR%%:*}\" | sed 's/[0-9]*$//'); RCOL=$(echo \"${ANCHOR##*:}\" | sed 's/[0-9]*$//')\n  SPAN=$(( $(col_num \"$RCOL\") - $(col_num \"$LCOL\") + 1 ))\n  MIN=$(( (${#TITLE} * 18 + 99) / 100 ))\n  [ \"$SPAN\" -lt \"$MIN\" ] && { echo \"REJECT Gate 2: chart[$i] title=${#TITLE} chars needs width ≥ $MIN, anchor spans $SPAN\"; exit 1; }\ndone\n```\n\nNarrower titles at preset `minimal` / `magazine` may clip earlier than the 0.18 factor — spot-check.\n\n**Gate 3 — Chart series names populated (no \"Series1\" in legend).**\n\n```bash\nfor i in $(seq 1 \"$CHART_COUNT\"); do\n  BAD=$(officecli get \"$FILE\" \"/Dashboard/chart[$i]\" --json | jq '[.data.results[0].children[]? | select(.type == \"series\") | select((.format.name // \"\") | test(\"^Series[0-9]+$\"; \"i\"))] | length')\n  [ \"$BAD\" -gt 0 ] && { echo \"REJECT Gate 3: chart[$i] has $BAD auto-named series\"; exit 1; }\ndone\n```\n\n**Gate 4 — CF rules on Data sheet (10+ rows).**\n\n```bash\nCF_COUNT=$(officecli query \"$FILE\" conditionalformatting --json | jq '.data.results | length')\n[ \"$CF_COUNT\" -lt 1 ] && { echo \"REJECT Gate 4: zero CF rules on 10+ row data sheet\"; exit 1; }\n```\n\nNote: `query conditionalformatting` is the canonical element name; `query cf` returns 0 (not an alias).\n\n**Gate 5 — activeTab and fullCalcOnLoad set.** Compare against real Dashboard index (Dashboard-at-index-0 is a true pass).\n\n```bash\nDASH_IDX=$(officecli query \"$FILE\" sheet --json | jq '[.data.results[].path] | index(\"/Dashboard\")')\nACTIVE=$(officecli get \"$FILE\" /workbook --json | jq '.data.results[0].format.activeTab // -1')\nFULLCALC=$(officecli get \"$FILE\" /workbook --json | jq -r '.data.results[0].format[\"calc.fullCalcOnLoad\"] // false')\n[ \"$ACTIVE\" != \"$DASH_IDX\" ] && { echo \"REJECT Gate 5: activeTab=$ACTIVE Dashboard=$DASH_IDX\"; exit 1; }\n[ \"$FULLCALC\" != \"true\" ] && { echo \"REJECT Gate 5: calc.fullCalcOnLoad=$FULLCALC — stale caches will ship\"; exit 1; }\n```\n\n**Gate 6 — Placeholder sweep.** No build-time tokens in rendered output.\n\n```bash\nLEAKS=$(officecli view \"$FILE\" text 2>/dev/null | grep -niE '\\{\\{|\\$fy\\$|<TODO>|xxxx|TBD' | wc -l | tr -d ' ')\n[ \"$LEAKS\" -gt 0 ] && { echo \"REJECT Gate 6: $LEAKS placeholder tokens\"; exit 1; }\n```\n\n**Gate 7 — Visual delivery floor (ported from xlsx).** Run `officecli view \"$FILE\" html` and Read the returned HTML path. Confirm:\n\n- No `###` in any Dashboard or Data cell (columns too narrow).\n- No truncated KPI labels, sheet tab names, or chart titles.\n- No placeholder tokens rendered as text (`$fy$24`, `{var}`, `<TODO>`, `xxxx`).\n- Pie / doughnut slices render with distinct fill colors (if collapsed in LibreOffice, verify in the user's target viewer before declaring broken — → see officecli-xlsx §Known Issues/Renderer caveats).\n- No empty chart anchors — every chart has a visible, plausible plot.\n- Dashboard sheet opens first (tab highlighted, active area scrolled to top).\n\nIf `view html` is blocked (renderer conflict, headless, port busy), Gate 7 is still **mandatory** — run ALL fallback checks:\n\n```bash\n# a) Token / ### sweep.\nofficecli view \"$FILE\" text 2>/dev/null | grep -nE '###|\\{\\{|<TODO>|\\$fy\\$|xxxx' && { echo \"REJECT Gate 7: tokens or ### present\"; exit 1; }\n# b) Per-KPI: cachedValue length × coef must fit col width. coef=0.55 fit-to-page, 0.85 otherwise.\nfor CELL in A2 C2 E2 G2; do\n  CV=$(officecli get \"$FILE\" \"/Dashboard/$CELL\" --json | jq -r '.data.results[0].format.cachedValue // .data.results[0].text // \"\"')\n  W=$(officecli get \"$FILE\" \"/Dashboard/col[${CELL%%[0-9]*}]\" --json | jq -r '.data.results[0].format.width // 0')\n  CAP=$(echo \"$W * 0.55\" | bc -l | awk '{print int($1)}')\n  [ \"${#CV}\" -gt \"$CAP\" ] && { echo \"REJECT Gate 7: $CELL '$CV' (${#CV} chars) > cap $CAP\"; exit 1; }\ndone\n# c) Rerun Gate 2 title × 0.18 ≤ anchor span.  d) Log which fallback was used and why.\n```\n\nGate 7 must **NEVER** be skipped — skipping ships `###` to the user.\n\nIf scene keywords include print / 一页 / board / 投资人 / 董事会, extend Gate 7 with a structural print-scope check:\n\n```bash\nif echo \"$USER_REQ\" | grep -qiE 'print|一页|投资人|董事会|board'; then\n  # Every non-Dashboard sheet must be hidden or veryHidden.\n  # query sheet --json carries the name in .preview (no .name/.state); visibility lives in\n  # each sheet's own get .format.hidden (absent => visible), so iterate + probe per sheet.\n  LEAKING=\"\"\n  while IFS=$'\\t' read -r SPATH SNAME; do\n    [ \"$SNAME\" = \"Dashboard\" ] && continue\n    HIDDEN=$(officecli get \"$FILE\" \"$SPATH\" --json | jq -r '.data.results[0].format.hidden // false')\n    [ \"$HIDDEN\" != \"true\" ] && LEAKING=\"$LEAKING $SNAME\"\n  done < <(officecli query \"$FILE\" 'sheet' --json | jq -r '.data.results[] | [.path, .preview] | @tsv')\n  [ -n \"$LEAKING\" ] && { echo \"REJECT Gate 7 print-scope: visible non-Dashboard sheet(s):$LEAKING — hide before delivery\"; exit 1; }\n  # Dashboard must carry an explicit Print_Area named range.\n  PA=$(officecli query \"$FILE\" 'namedrange[name=\"_xlnm.Print_Area\"]' --json | jq '.data.results | length')\n  [ \"$PA\" -ge 1 ] || { echo \"REJECT Gate 7 print-scope: no _xlnm.Print_Area set\"; exit 1; }\nfi\n```\n\nThe user opens the file in their target viewer (Office / WPS / Numbers) for the final print preview — the skill does not render export artefacts.\n\n**Gate 8 — Formula sanity (cachedValue real, not stale/error).** `fullCalcOnLoad=true` refreshes at runtime, NOT build-time cache — so every formula cell must carry a non-empty, non-zero, non-error `cachedValue` now.\n\n```bash\nfor CELL in A2 C2 E2 G2; do\n  JSON=$(officecli get \"$FILE\" \"/Dashboard/$CELL\" --json)\n  [ -z \"$(echo \"$JSON\" | jq -r '.data.results[0].format.formula // \"\"')\" ] && continue\n  CV=$(echo \"$JSON\" | jq -r '.data.results[0].format.cachedValue // \"\"')\n  case \"$CV\" in\n    \"\" | \"0\" | \"#DIV/0!\" | \"#REF!\" | \"#N/A\" | \"#VALUE!\" | \"#NAME?\" | \"null\")\n      echo \"REJECT Gate 8: $CELL cachedValue='$CV' — re-issue formula or close+reopen\"; exit 1 ;;\n  esac\ndone\n```\n\nIf a KPI is genuinely zero (e.g. \"terminations this quarter\" = 0), whitelist it in the loop and document — default assumption is \"zero is broken\".\n\nIf anything fails, fix at source, re-run the full cycle.\n\n### Honest limits\n\nScatter charts do not accept `series1.xValues` (UNSUPPORTED) — feed the x-axis via `categories` / `series1.categories`. LibreOffice chart color drift / pie-slice collapse / checkbox double-box are viewer artifacts — spot-check in Office / WPS / Numbers first.\n\n## Reference\n\n- **Shorthand `--type` at `add`:** `chart`, `sparkline`, `databar`, `colorscale`, `iconset`, `formulacf`. CF rules map to `help xlsx conditionalformatting`; path suffix `/Sheet/cf[N]`.\n- **Full schemas live in help:** `officecli help xlsx chart` / `sparkline` / `conditionalformatting`. This skill does not mirror them.\n- **DeferredAddKeys (add-only):** `combosplit`, `holesize`. See D-1. (`preset`, `trendline`, `referenceline`, `axisNumFmt` now work on `set` too — help shows `[add/set]`.)\n- **Build order:** charts + sparklines + CF + tabColors first → `calc.fullCalcOnLoad=true` via high-level `set` → `raw-set activeTab` **LAST** (after all sheets exist).\n\n## Known Issues & Pitfalls\n\n### Dashboard-specific\n\n| # | Issue | Mitigation |\n|---|---|---|\n| D-1 | `combosplit` is a DeferredAddKey — works on `add` only. (`preset`, `referenceline`, `trendline`, `axisNumFmt` now apply on `set` too — help shows `[add/set]`.) | Set `combosplit` at `add` time; cannot apply after the fact — remove + re-add. The other four can be applied or changed post-creation via `set`. |\n| D-2 | `referenceline` format is `value:color:label:dash` (color BEFORE label). `\"0:Break-Even:FF0000:dash\"` fails `Invalid color value`. | Order is value, color, label, dash. |\n| D-3 | Scatter charts do NOT accept `series1.xValues` (UNSUPPORTED). Feed the x-axis through `categories` / `series1.categories`. | `--prop series1.categories=\"Sheet1!A2:A13\"` (or `--prop categories=\"Sheet1!A2:A13\"`) |\n| D-4 | `formulacf` honors `font.bold` / `font.italic` (written to the dxf font and surfaced on readback), alongside `fill` and `font.color`. | Use any of `fill` / `font.color` / `font.bold` / `font.italic` to signal a CF rule. |\n| D-5 | Dashboard column widths default to 8.43 — KPI values at 24pt bold show `###` | Size by cachedValue bracket: 4–6 digits → 22–24; 7–9 digits (million) → 26–30; 10+ digits (亿 / billion) → 32–36; 百亿 / 10-digit + currency symbol + fit-to-page landscape → **40–44**. Formula `ceil((visible_chars+2)*1.3)` is a starting point; always verify via Gate 7 fallback b). Sparkline columns: 12. |\n| D-6 | `raw-set activeTab` must be the LAST mutation. Inserting before all sheets exist shifts indices. | Finish all sheets / charts / CF / sparklines / tabColors, then `raw-set`. |\n| D-7 | `calc.fullCalcOnLoad` via `raw-set` creates duplicate `<calcPr>` → validate fails | Use `officecli set \"$FILE\" / --prop calc.fullCalcOnLoad=true`. |\n| D-8 | LibreOffice does not evaluate hidden-column formulas at render → charts referencing hidden cells render blank | Aggregate into a visible Summary sheet, chart reads from Summary. Hide only columns that are not chart sources. |\n| D-9 | `chartType=pie` blank-renders in LibreOffice | Use `doughnut` as the safe substitute for part-of-whole breakdowns. |\n| D-10 | `SUMIFS` / `AVERAGEIFS` with date criteria fails silently if the criterion is a string | Wrap with `DATE()` or `DATEVALUE()`: `=SUMIFS(B2:B13,A2:A13,DATE(2025,1,5))`. |\n| D-11 | Summary sheet percentage formulas display as raw decimals (0.098) without `numFmt` | Set `numFmt=\"0.0%\"` at the same `set` call as the formula. |\n| D-12 | `import --header` sets freeze + AutoFilter but does NOT set column widths. | Set widths on `col[]`. `numFmt` on a `col[]` path now applies a column-level style (`<col s=...>`, schema-valid, reads back as `numberformat`); it formats blank cells in the column. Cells with their own style still need a per-cell-range `numFmt`. |\n| D-13 | Sparkline `highpoint` is a bool (highlight on/off), not a color. `--prop highpoint=FF0000` errors `Invalid boolean value` | `--prop highPoint=true --prop highMarkerColor=FF0000`. Same pattern for lowPoint / firstPoint / lastPoint and their *MarkerColor. |\n| D-14 | Sparkline cross-sectional data is meaningless (a region or department has no ordering) | Skip sparklines unless rows are a sequential time-series (dates, months, quarters). |\n| D-15 | Empty chart `add` is rejected (`Chart requires a 'data' property`) at the CLI layer — legacy skills that relied on silent accept will fail here | Always provide `series1.values=` / `dataRange=` / inline `data=` at chart `add` time. Treat Gate 2 seriesCount check as a belt-and-braces verification. |\n| D-16 | `fullCalcOnLoad=true` guarantees a **runtime** recalc when the end user opens the file; it does NOT refresh the build-time `cachedValue` in XML. Build sequence `set B=100 → set E==B+D → fix B=150` leaves `E.cachedValue` stale (board sees \"Net Change = 0\"). | After all upstream edits are final, re-issue every downstream formula (`officecli set \"$FILE\" /Sheet/E2 --prop formula==B2+D2`) OR `close` + re-open the file. Gate 8 verifies. |\n| D-17 | The built-in calc engine does NOT evaluate `SUMPRODUCT` with array-predicate form `SUMPRODUCT((A2:A97=X)*C2:C97*D2:D97)` — cachedValue stays `0`/`null`, Gate 8 rejects. Runtime Excel / WPS compute fine, but board-delivered XLSX with stale cache still ships `0`. | Rewrite as helper column + `SUMIF`: `F2==C2*D2` on source sheet, then `=SUMIF(B:B, \"Region X\", F:F)`. Or pre-aggregate in Summary sheet and chart from there. |\n\n### Inherited (pointer only)\n\nCross-sheet `!` trap, chart `anchor` / series immutability after create → see officecli-xlsx §Known Issues.\n","frontmatter":{"name":"officecli-data-dashboard","description":"Use this skill to build a multi-element Excel dashboard — Dashboard sheet on open, multiple formula-driven KPI cards, multiple charts, sparklines, and conditional formatting — from CSV or tabular input. Trigger on: 'dashboard', 'KPI dashboard', 'analytics dashboard', 'executive dashboard', 'metrics dashboard', 'CSV to dashboard', 'data visualization'. Output is a single .xlsx. Scene-layer on officecli-xlsx: inherits every xlsx hard rule. DO NOT invoke for: a single budget tracker / one-sheet CSV-with-formatting (use xlsx), a 3-statement / DCF / LBO financial model (use financial-model), a weekly report with at most 1 chart and fewer than 10 rows (use xlsx)."},"isInternal":false,"tokens":8405,"sizeBytes":29886},{"name":"SKILL.md","path":"skills/officecli-docx/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-docx/SKILL.md","title":"officecli-docx","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-docx\ndescription: \"Use this skill any time a .docx file is involved -- as input, output, or both. This includes: creating Word documents, reports, letters, memos, or proposals; reading, parsing, or extracting text from any .docx file; editing, modifying, or updating existing documents; working with templates, tracked changes, comments, headers/footers, or tables of contents. Trigger whenever the user mentions 'Word doc', 'document', 'report', 'letter', 'memo', or references a .docx filename.\"\n---\n\n# OfficeCLI DOCX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good docx looks like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help docx                         # List all docx elements\nofficecli help docx <element>               # Full element schema (e.g. paragraph, field, numbering, watermark, toc)\nofficecli help docx <verb> <element>        # Verb-scoped (e.g. add field, set section)\nofficecli help docx <element> --json        # Machine-readable schema\n```\n\nHelp is pinned to the installed CLI version. When this skill and help disagree, **help is authoritative**.\n\n## Mental Model\n\nA `.docx` is a ZIP of XML parts (`document.xml`, `styles.xml`, `numbering.xml`, `header*.xml`, `footer*.xml`, `comments.xml`, …). Everything the user sees — headings, tables, page numbers, TOC, tracked changes — is XML inside that ZIP. `officecli` gives you a semantic-path API (`/body/p[1]/r[2]`) over it, so you almost never touch raw XML; when you must, use `raw-set` (see the XML appendix).\n\n## Shell & Execution Discipline\n\ndocx paths contain `[]`; some prop values contain `$`. Both are shell metacharacters. Escaping happens at three layers — keep them separate.\n\n1. **Shell.** ALWAYS quote element paths: `\"/body/p[1]\"`, not `/body/p[1]` (zsh/bash glob `[N]`). Single-quote any value containing `$`: `--prop text='$50M'` — at any length, the whole value inside one pair of single quotes. Unquoted `$50M` is stripped to `M`; mixing `'…$var…'` and `\"…$50…\"` on one long string is where the `$50` silently vanishes.\n2. **CLI (`text=`).** The two-char escapes `\\n`, `\\v` and `\\t` ARE interpreted in `--prop text=` (same rule as pptx): `\\n` starts a **new paragraph** (each new `<w:p>` inherits the source paragraph's style), `\\v` is a **soft line break** within one paragraph (`<w:br/>`), `\\t` a `<w:tab/>`. Double the backslash (`\\\\n`) for a literal backslash-n (rarely wanted). Row-level table shortcuts `c1…cN` are the exception: there `\\n` is a `<w:br/>` line break inside the cell (see recipe (e)).\n3. **JSON (batch).** Batch does NOT interpret the two-char escapes — a JSON `\"\\\\n\"` or `\"\\\\v\"` lands as literal text. Use JSON escapes for the real characters: `\"\\n\"` (a newline) starts a new paragraph, `\"\\u000b\"` is a soft line break.\n\nIf in doubt, `view text` after writing and compare character-for-character.\n\n**Incremental execution.** `officecli` mutates the file on every call. Run commands one at a time and check each exit code — a 50-command script that fails at command 3 cascades silently. After any structural op (new style, table, TOC, section break) run `get` on it before stacking more.\n\n**Open/save lifecycle:** `officecli open <file>` at the start, `officecli save <file>` at the end to flush to disk — `save` only writes and leaves the resident warm for follow-up edits; reach for `officecli close <file>` only to release the resident on a one-shot handoff. Both are always safe (never error or lose work). For many paragraphs of one style, use `batch` (one pass for the whole array). **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (python-docx, Word, a renderer, delivery).\n\n**`$FILE` convention.** All commands use `\"$FILE\"` — set it once (`FILE=\"your-doc.docx\"`). Never copy a literal `doc.docx` / `review.docx` into output — always substitute your actual target.\n\n## Requirements for Outputs\n\nDeliverable standards every document MUST meet — know these before reaching for a command.\n\n**Clear hierarchy.** Every non-trivial document has Title → Heading 1 → Heading 2 → body, not a wall of unstyled `Normal` paragraphs. If `view outline` shows one flat list, the hierarchy is missing.\n\n**Explicit heading sizes** (Word default style sizes drift between templates): **H1 ≥ 18pt** (20pt for long reports), H2 = 14pt bold, H3 = 12pt bold, body = 11–12pt, line spacing 1.15–1.5x. Prefer `style=Heading1` over inline sizes so a retheme touches the definition once — but set explicit sizes when you can't trust the template's styles.\n\n**One body font, one accent.** One readable body font (Calibri, Cambria, Georgia, Times New Roman); accent color for heading emphasis or table headers, not rainbow formatting.\n\n**Spacing through properties.** Use `spaceBefore` / `spaceAfter` on paragraphs. Rows of empty paragraphs break pagination and are flagged by `view issues`.\n\n**Typographic quality.** New content uses curly quotes (`'` `'` `\"` `\"`), not ASCII — Unicode directly or XML entities (`&#x2018;`/`&#x2019;`/`&#x201C;`/`&#x201D;`) inside `raw-set`. En-dash `–` for ranges (`2024–2026`), em-dash `—` for parenthetical breaks.\n\n**Headers, footers, page numbers on any document > 1 page.** Page numbers go through a live `PAGE` field (`--prop field=page`), never the literal text \"Page 1\" — the CLI injects `<w:fldChar>` for you (see Headers & Footers).\n\n**Preserve existing templates.** When editing a file that already has a look, match it — existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY document)\n\nBefore declaring done, run `officecli view \"$FILE\" html` and Read the returned HTML path to confirm ALL of these:\n\n- **No placeholder tokens rendered as data.** `$xxx$`, `{var}`, `{{name}}`, `<TODO>`, `lorem`, `xxxx` must never appear in a heading, body, cover, TOC, caption, header, or footer. A literal `{name}` meant for a human to fill belongs inside a visible instruction paragraph (\"Replace `{name}` before sending\"), never as finished content.\n- **No truncated titles or overflowing cells.** Widen the column or set `wrapText` rather than trimming content.\n- **TOC present when the document has 3+ headings** (`--type toc`).\n- **Cover page ≥ 60% filled, last page ≥ 40% filled.** Pad a thin cover with subtitle / author / date / scope / key highlights; pad a \"Thank you\" last page with conclusion / next steps / contact / legal.\n- **No `\\$`, `\\t`, `\\n` literals in document text.** If `view text` shows these, a shell-escape layer leaked — delete the paragraph and re-enter it.\n\nIf any fails, STOP and fix before declaring done.\n\n## Common Workflow\n\nSix steps. Every non-trivial build follows this shape.\n\n1. **Open the file.** `officecli open \"$FILE\"` (resident default). New file: `officecli create \"$FILE\"` first.\n2. **Orient.** Existing file: `officecli view \"$FILE\" outline` — heading tree, section count, whether a TOC / watermark / tracked changes already exist. Never edit blind.\n3. **Build incrementally.** Structural first, content next, formatting last: styles & numbering defs → sections / page setup → headings & body → tables / images / fields / TOC → headers / footers → comments. After each structural op, `get` it back before stacking on top.\n4. **Format to spec.** Explicit heading sizes, spacing, widths, alignment, tabs, list indents — formatting is part of the deliverable, not optional polish.\n5. **Save, then trust structure over cached text.** `officecli save \"$FILE\"` writes the XML. TOC / PAGE / NUMPAGES / SEQ / PAGEREF fields carry **cached values** that may be stale or empty until a human recalculates (F9 in Word). Confirm fields *exist* (`get --depth 3` finds `<w:fldChar>`) rather than trusting the visible text.\n6. **QA — assume there are problems.** You are done after one fix-and-verify cycle finds zero new issues, not when your last command exited 0. See QA.\n\n## Quick Start\n\nMinimal viable docx: a heading, a body paragraph, a subheading, and a footer with a live page-number field. Adapt, don't copy-paste — your file, your content.\n\n```bash\nFILE=\"review.docx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Q4 2026 Review\" --prop style=Heading1 --prop size=20pt --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Revenue grew 18% year-over-year, ahead of plan.\" --prop size=11pt --prop spaceAfter=8pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Key Drivers\" --prop style=Heading2 --prop size=14pt --prop bold=true --prop spaceBefore=12pt --prop spaceAfter=6pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Enterprise renewals, upsell, and a new EMEA region.\" --prop size=11pt\nofficecli add \"$FILE\" / --type footer --prop type=default --prop size=9pt --prop text=\"Page \" --prop field=page\nofficecli set \"$FILE\" \"/footer[1]/p[1]\" --prop align=center\nofficecli save \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified: `validate` returns `no errors found`; `get /footer[1] --depth 3` shows the 5-run PAGE field chain (begin / instrText / separate / cached value / end).\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` tells you what's already there; jump into `view text` / `get` / `query` once you know where to look.\n\n```bash\nofficecli view \"$FILE\" outline            # heading tree, section count, table/image counts, watermark, tracked-changes presence — orient here first\nofficecli view \"$FILE\" html               # Read the returned HTML path: first visual check after a batch of edits (hierarchy, empty-para spacing, missing TOC)\nofficecli view \"$FILE\" text --start 1 --end 80   # text for content QA; paths shown as [/body/p[N]] so you can jump back with get\nofficecli view \"$FILE\" annotated          # values + style/font/size + warnings per run\nofficecli view \"$FILE\" stats              # paragraph counts, font usage, style distribution\nofficecli view \"$FILE\" issues             # empty paras, missing alt text, spacing anomalies\n```\n\n`officecli watch \"$FILE\"` keeps a live preview running for the human user to open at their discretion — agent self-check uses `view html`. Final visual verification is the user opening the `.docx` in Word / WPS / Pages.\n\n**Inspect one element.** XPath-style semantic paths (1-based). Always quote — shells glob `[N]`. Use `[last()]` (with parens) for the last element; `[last]` errors. Add `--json` for machine output.\n\n```bash\nofficecli get \"$FILE\" /                          # document root: metadata, page setup\nofficecli get \"$FILE\" \"/body/p[1]\"                # one paragraph\nofficecli get \"$FILE\" \"/body/p[1]/r[1]\"           # one run (character-level formatting)\nofficecli get \"$FILE\" \"/body/tbl[1]\" --depth 3    # table with rows and cells\nofficecli get \"$FILE\" \"/footer[1]\" --depth 3      # footer — check for fldChar\nofficecli get \"$FILE\" \"/styles/Heading1\"          # style definition\nofficecli get \"$FILE\" /numbering --depth 2        # numbering abstractNum + num bindings\n```\n\n**Query across the document.** CSS-like selectors, for systematic checks rather than hand-walking. Operators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists). Full reference: `officecli query --help`.\n\n```bash\nofficecli query \"$FILE\" 'paragraph[style=Heading1]'       # all H1s\nofficecli query \"$FILE\" 'p:contains(\"quarterly\")'         # text match\nofficecli query \"$FILE\" 'p:empty'                         # empty paragraphs (clutter)\nofficecli query \"$FILE\" 'image:no-alt'                    # accessibility gaps\nofficecli query \"$FILE\" 'paragraph[size>=24pt]'           # numeric comparison\nofficecli query \"$FILE\" 'field[fieldType!=page]'          # fields other than PAGE\n```\n\n`query --json` wraps results in `.data.results[]` — `jq '.data.results | length'` to count.\n\n**Large documents.** Navigate by heading with `view outline` and jump with `query`; don't dump the whole body into context.\n\n## Creating & Editing\n\nVerbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`, `raw-set` (last-resort XML). Ninety percent of a build is paragraphs, runs, tables, a couple of images, a TOC, and a footer.\n\n### Paragraphs, runs, styles\n\nA paragraph (`p`) is a block; a run (`r`) is a span of consistent character formatting inside it. Set paragraph-level props (style, alignment, spacing, indent) on the `p`; set font / size / color / bold on the `r`.\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1 --prop size=18pt --prop bold=true --prop spaceAfter=12pt\nofficecli set \"$FILE\" \"/body/p[1]/r[1]\" --prop color=1F4E79\n```\n\nUse `spaceBefore` / `spaceAfter` for vertical spacing — never chains of empty paragraphs. For left indent use `--prop indent=720` (twips), `firstLineIndent=360` for first line, `hangingIndent=720` for hanging; leading spaces fire `view issues`.\n\n### Tables\n\nTables are `/body/tbl[N]` with rows `tr[N]` and cells `tc[N]`. Add with row/column counts, then fill.\n\n```bash\nofficecli add \"$FILE\" /body --type table --prop rows=4 --prop cols=3 --prop width=100%\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop header=true --prop c1=Quarter --prop c2=\"Revenue\" --prop c3=\"Growth\"\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[1]/r[1]\" --prop bold=true\n```\n\nRow-level `set` supports `height`, `header`, and `c1 / c2 / … / cN` text shortcuts (`cN` generalises to any column count). Cell formatting (bold, fill, color) goes on the cell's paragraph / run — **not** row-level. For per-cell borders, set cell-level `border.*` on the `tc` (`--prop border.bottom=\"single;6;000000;0\"`), or paragraph-level `pbdr.*` on the inner paragraph.\n\n**Horizontal rule = a paragraph bottom border, never a 1-row table.** A table-as-divider renders as an empty min-height box (worst in headers/footers). Use `pbdr.bottom` (`STYLE;SIZE;COLOR`) on the paragraph instead:\n\n```bash\nofficecli set \"$FILE\" \"/body/p[3]\" --prop pbdr.bottom=\"single;6;2E75B6\"\n```\n\n### Lists (bullets, numbered, multi-level)\n\nFor single-level bullets/numbers, set `listStyle` on the paragraph (`listStyle` is a paragraph prop, NOT a run prop — common mistake):\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"First item\" --prop listStyle=bullet\n```\n\nFor multi-level (legal-style 1 / 1.1 / 1.1.1), add an `abstractNum`, then a `num`, then reference the `numId` per paragraph:\n\n```bash\nofficecli add \"$FILE\" /numbering --type abstractnum --prop format=decimal     # → abstractNum id=0\nofficecli add \"$FILE\" /numbering --type num --prop abstractNumId=0             # → num id=1\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Section one\" --prop numId=1 --prop ilvl=0\n```\n\nIDs are 0-based: the first `abstractNum` is id=0; the `num` references it via `abstractNumId=0` and is itself assigned id=1. A non-existent `abstractNumId` errors, so check ids after creating. Verify with `officecli query \"$FILE\" 'paragraph[numId>0]'`. See `help docx abstractnum` / `help docx num` for level and format options.\n\n### Tab stops (signature lines, leader rows)\n\nTab stops are a first-class `tab` child of the paragraph; `pos` accepts `6in`/`6cm`/twips, `val` ∈ `left`/`center`/`right`, `leader` ∈ `none`/`dot`/`hyphen`/`underscore`. See `help docx tab`.\n\n```bash\nofficecli add \"$FILE\" \"/body/p[1]\" --type tab --prop pos=6in --prop val=right --prop leader=dot\n```\n\n**Leader caveat.** `leader=dot` alone emits no dots — the leader renders only when a real `<w:tab/>` character sits in a run between the text and the tab stop. Put one there with `\\t` in the text: define the stop (`add tab --prop pos=6in --prop val=right --prop leader=dot`), then `--prop text=\"Chapter 1\\t12\"` — the `\\t` becomes the `<w:tab/>` and dots fill to the right-aligned page number. (Literal `text=\"Chapter 1 ......... 12\"` also ships, but a real tab stop aligns cleanly.)\n\n### Fields (PAGE / NUMPAGES / DATE / MERGEFIELD / REF)\n\nFields are live values computed at render time. `fieldType` picks the field; `name` supplies the target (merge name or `ref` bookmark); `format` / `instr` add switches.\n\n| Field | Use | Example |\n|---|---|---|\n| `page` | current page number | `--prop field=page` on footer, or `--prop fieldType=page` inline |\n| `numpages` | total pages | `--prop field=numpages` / `--prop fieldType=numpages` |\n| `date` | today | `--prop fieldType=date --prop format='yyyy-MM-dd'` |\n| `mergefield` | template merge token | `--prop fieldType=mergefield --prop name=CustomerName` |\n| `ref` | cross-reference to a bookmark | `--prop fieldType=ref --prop name=bookmarkName` |\n\nFull `fieldType` enum (30+ values incl. `pageref`, `seq`, `styleref`, `docproperty`, `createdate`, …) is in `help docx field`. **There is NO `fieldInstr` fieldType** — use the `instruction` prop for raw field instruction text when typed shortcuts fall short. Picture switches (`MERGEFIELD Amount \\# \"#,##0.00\"`, `DATE \\@ \"yyyy年MM月\"`) go via `--prop instruction='…'` (mergefield's `format` prop is ignored with a warning — use `instruction`).\n\n```bash\nofficecli add \"$FILE\" \"/body/p[3]\" --type field --prop fieldType=mergefield --prop name=customer_name\n# Renders «customer_name» — visible placeholder, replaced in Word at mail-merge time.\n```\n\n**MERGEFIELD templates: never render placeholder literals.** A `{{customer_name}}` or `$NAME$` shown as body text is a failed template the recipient sees — insert a real MERGEFIELD (above), or confine literal tokens to an obvious instruction paragraph. Confirm with `query 'field[fieldType=mergefield]'`.\n\n**SEQ / PAGEREF / TOC field values.** officecli doesn't store rendered field values at write time. Recompute by what each path needs:\n\n- **SEQ numbering** (`Figure 1/2/3`): `officecli set \"$FILE\" / --prop recalcFields=seq` counts SEQ fields in body document order and writes the cached values (`evaluated` flips true; switches/formats in `help docx document`). Heading-relative `\\s` and SEQ in headers/footers defer to Word.\n- **PAGE / PAGEREF / NUMPAGES / TOC page numbers** need pagination, which officecli has no engine for — `officecli set \"$FILE\" /settings --prop updateFields=true` defers them to Word on open.\n\nUse both on a multi-figure document. Academic papers: see the `officecli-academic-paper` skill.\n\n### Headers & Footers (page numbering)\n\nSingle-command pattern — the CLI injects `<w:fldChar>`, so you never compose the field by hand:\n\n```bash\n# Empty first-page footer — auto-enables differentFirstPage so the cover has no page number\nofficecli add \"$FILE\" / --type footer --prop type=first --prop text=\"\"\n# Default footer with live page number\nofficecli add \"$FILE\" / --type footer --prop type=default --prop align=center --prop size=9pt --prop text=\"Page \" --prop field=page\n```\n\nWhen both exist, the default footer is `/footer[2]`; alone it is `/footer[1]`. **Verify**: `get --depth 3` must show `fldChar` children, not just a run with literal `\"Page\"` (`view outline` prints \"Footer: Page\" for both live fields AND static text — don't rely on it). Do NOT `set --prop differentFirstPage=true` — that prop is unsupported (exit 2 with `WARNING: UNSUPPORTED`; the element itself is still created — check `success` in `--json`); adding a first-type footer flips the bit. For composite **Page X of Y**, see recipe (b).\n\n### Table of Contents\n\nFor any document with 3+ headings, first verify that the requested range has real sources. A TOC source must use a built-in Heading style or a custom paragraph style with `outlineLvl`; bold or large Normal text is not a source. If no eligible source exists, stop and fix the heading structure instead of inserting a TOC that renders \"Error! No table of contents entries found.\"\n\n```bash\n# Built-in Heading styles are TOC sources.\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Introduction\" --prop style=Heading1\n# Custom paragraph styles need an outline level (0 = Heading 1).\nofficecli add \"$FILE\" /styles --type style --prop id=ThesisH1 --prop type=paragraph --prop outlineLvl=0\n# Add the TOC only after its sources exist.\nofficecli add \"$FILE\" /body --type toc --prop levels=\"1-3\" --prop title=\"Table of Contents\" --prop hyperlinks=true --index 0\n```\n\nPage numbers need pagination; OfficeCLI cannot calculate TOC page numbers itself. Set `updateFields=true` so Word recomputes the TOC (and all fields) on open. Without a Word-compatible field engine, report the TOC as dynamic and uncomputed; do not claim ready page numbers or guess a static TOC.\n\n```bash\nofficecli set \"$FILE\" /settings --prop updateFields=true\n```\n\nAddress the TOC directly with `/toc[1]` or `/tableofcontents` for `get`/`set`/`remove`.\n\n### Images\n\nPictures go inside a run. Alt text is mandatory for accessibility — pass `alt` directly at create time:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[5]\" --type picture --prop src=logo.png --prop width=1.5in --prop alt=\"Acme logo\"\n```\n\nConfirm `officecli query \"$FILE\" 'image:no-alt'` is empty before delivery.\n\n### Charts\n\nFor data, add a **native chart** — editable, themeable, accessible, re-renders in Word — never a flat PNG screenshot of a chart. `data=\"Label:v1,v2,…\"` per series; one `data=` per series (or `series1=`/`series2=`).\n\n```bash\nofficecli add \"$FILE\" /body --type chart --prop chartType=bar --prop title=\"Revenue by Region\" --prop categories=\"EMEA,APAC,Americas\" --prop data=\"2026:120,150,180\"\n```\n\n`chartType` ∈ bar / column / line / pie / area / scatter (`help docx chart` for axis/legend/series styling). A PNG via `--type picture` is only a fallback for an exotic chart officecli can't build.\n\n### Hyperlinks and bookmarks\n\nExternal links go via `hyperlink`:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[2]\" --type hyperlink --prop url=\"https://example.com\" --prop text=\"our site\"\n```\n\n**Internal links** (to a bookmark) use `--prop anchor=bookmarkName` — not a `#fragment` in `url`:\n\n```bash\nofficecli add \"$FILE\" \"/body/p[2]\" --type hyperlink --prop anchor=chapter1 --prop text=\"See Chapter 1\"\n```\n\nPairing a `PAGEREF` field with visible text is the alternative. See `help docx hyperlink` / `help docx bookmark`.\n\n### Sections and page setup\n\nDocument root `/` carries page setup (`pageWidth`, `pageHeight`, margins, in twips). Multi-section documents (landscape insert, columns) add a `section` break — see `help docx section`. Both camelCase (`pageWidth`, canonical) and lowercase alias (`pagewidth`) are accepted; prefer camelCase.\n\n```bash\nofficecli set \"$FILE\" / --prop pageWidth=12240 --prop pageHeight=15840 --prop marginTop=1440 --prop marginLeft=1440\n# Newspaper-style multi-column flow (columnSpace in twips; 720 = 0.5in):\nofficecli set \"$FILE\" / --prop columns=2 --prop columnSpace=720\n```\n\n### Forcing page breaks\n\nUse exactly one page-break mechanism per logical boundary. The default is `pageBreakBefore=true` on the target heading. Alternatively, insert one explicit `pagebreak` before the heading. Never combine both mechanisms for the same boundary: the resulting double break can create a blank page. Never set `pageBreakBefore` on `p[last()]` immediately after adding a `pagebreak`.\n\n```bash\n# Default: apply the break directly to the heading.\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Introduction\" --prop style=Heading1 --prop pageBreakBefore=true\n```\n\n`--prop break=newPage` is an alias for `pageBreakBefore=true`. Preview with `view html` and check for blank pages.\n\n### Report-level recipes\n\nPatterns that come up on every long-form report. Each has been executed and `validate`-passed.\n\n**(a) Rich cover page — hit the ≥ 60% filled floor.** Stack a confidentiality banner, title, subtitle, client/project/date block, and a key-themes strip, then force the next section onto a new page:\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"CONFIDENTIAL — CLIENT USE ONLY\" --prop align=center --prop size=9pt --prop color=C00000 --prop spaceAfter=24pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Strategic Growth Review\" --prop style=Title --prop size=32pt --prop bold=true --prop align=center --prop font=Cambria --prop spaceAfter=8pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"FY26 Outlook and Scenario Planning\" --prop italic=true --prop size=16pt --prop align=center --prop spaceAfter=36pt\nofficecli add \"$FILE\" /body --type paragraph --prop text='Prepared for: Acme Corp. Leadership Team' --prop align=center --prop size=11pt\nofficecli add \"$FILE\" /body --type paragraph --prop text='Engagement: 2026-04 — 2026-06' --prop align=center --prop size=11pt --prop spaceAfter=36pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Key themes: 1) margin resilience, 2) EMEA expansion, 3) capital allocation.\" --prop align=center --prop italic=true --prop size=10pt\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1 --prop pageBreakBefore=true\n```\n\n**(b) Page X of Y footer — composite PAGE + NUMPAGES.** Add the footer paragraph, then three child ops build `Page <X> of <Y>` live. The official `help docx footer` recipe.\n\n```bash\nofficecli add \"$FILE\" / --type footer --prop type=default --prop text=\"Page \" --prop align=center --prop size=9pt\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type field --prop fieldType=page\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type run --prop text=\" of \"\nofficecli add \"$FILE\" \"/footer[1]/p[1]\" --type field --prop fieldType=numpages\nofficecli get \"$FILE\" \"/footer[1]/p[1]\" --depth 1 | grep -o fldChar | wc -l   # expect ≥ 4; use grep -o ... | wc -l, NOT grep -c (single-line XML returns 1)\n```\n\n**(c) Header row with fill and white bold text.** Order matters — populate header cell text FIRST (runs don't exist in empty cells; a `set …/tc[N]/p[1]/r[1]` on an empty cell errors \"No r found\"), THEN cell fill, THEN run formatting:\n\n```bash\nofficecli add \"$FILE\" /body --type table --prop rows=5 --prop cols=4 --prop width=100%\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop header=true --prop c1=Quarter --prop c2=Revenue --prop c3=Growth --prop c4=Status\nfor col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[$col]\" --prop fill=1F4E79\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[$col]/p[1]/r[1]\" --prop bold=true --prop color=FFFFFF\ndone\nfor row in 3 5; do for col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[$row]/tc[$col]\" --prop fill=D9E2F3      # zebra stripe\ndone; done\n```\n\n**(d) Financial table — right-align numbers, bold totals, bottom border on total row.**\n\n```bash\nfor row in 2 3 4 5; do for col in 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[$row]/tc[$col]/p[1]\" --prop align=right\ndone; done\nfor col in 1 2 3 4; do\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[5]/tc[$col]/p[1]/r[1]\" --prop bold=true\n  officecli set \"$FILE\" \"/body/tbl[1]/tr[4]/tc[$col]/p[1]\" --prop pbdr.bottom=\"single;6;000000;0\"\ndone\n```\n\n**(e) Cell with multiple bullets (SWOT / risk matrix).** `c1=\"a\\nb\"` gives a `<w:br/>` line break within **one** paragraph — fine for plain multi-line text, but bullets need separate paragraphs. Seed the first via `set c1=`, then `add paragraph` (with `listStyle=bullet`) under the cell per subsequent bullet:\n\n```bash\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]\" --prop c1=\"Installed base of 18k enterprise seats\"\nofficecli add \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]\" --type paragraph --prop text=\"Margin structure above peer median\" --prop listStyle=bullet\nofficecli set \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[1]\" --prop listStyle=bullet\n```\n\nIf the seeded line lands at the bottom, re-order: `officecli move \"$FILE\" \"/body/tbl[1]/tr[1]/tc[1]/p[N]\" --index 0`.\n\n**(f) TOC without a field engine.** A CLI-only pipeline cannot calculate reliable page numbers. Keep the live TOC, set `updateFields=true`, and tell the recipient to open the document in Word or another compatible field engine. Do not replace it with guessed static page numbers.\n\n### Template delivery — separating Template Notes from end-user content\n\nHR / legal / vendor templates carry internal-only guidance (\"replace `{{CompanyName}}`\") that must NOT ship. Two working patterns:\n\n- **Trailing \"Template Notes\" section** under a clear `Heading 1` (\"Template Notes for HR Users\") with all instructions below it; before distribution, `remove` from the heading downward (locate with `query 'paragraph[style=Heading1]:contains(\"Template Notes\")'`).\n- **Bookmark-bounded internal section** between `__template_notes_start` / `_end` bookmarks; at delivery `raw-set` removes everything between the anchors.\n\nDelivery gate for templates: after removal, `query 'p:contains(\"Template Notes\")'` AND `query 'p:contains(\"{{\")'` both return empty. If a notes paragraph survives, a downstream employee reads internal language.\n\n### Advanced / specialty topics (skip if you are writing a report)\n\nReports, memos, letters, proposals, and HR templates don't need this. Keep reading only if your document is academic (equations, footnotes, bibliography), reviewed (comments, tracked changes), or marked (watermark).\n\n**Equations and footnotes.** `--type equation` takes LaTeX — `\\frac`, `\\sum`, Greek, `\\mathit`, `\\mathcal` all render. By default it creates a standalone `/body/oMathPara[N]` display block; pass `--prop mode=inline` with a paragraph parent path (`add \"/body/p[N]\" --type equation --prop formula=… --prop mode=inline`) to drop an inline `<m:oMath>` into running text. Footnotes auto-number by paragraph index. Bibliography hanging indent: `firstLineIndent=-720 indent=720` per entry.\n\n```bash\nofficecli add \"$FILE\" /body --type equation --prop formula=\"\\\\frac{a}{b} + \\\\sum_{i=1}^{n} x_i\"\nofficecli add \"$FILE\" \"/body/p[3]\" --type footnote --prop text=\"See Appendix A for methodology.\"\n```\n\n**Comments and tracked changes.** Bulk accept/reject: `set \"$FILE\" /revision --prop revision.action=accept` (or `--prop revision.action=reject`); narrow with a selector like `/revision[@author=Alice]` or `/revision[@type=ins]`. Locate individual changes with `query ins` and `query del` (`trackedchange` is not a selector). Create tracked changes on a run with `--prop revision.type=ins|del --prop revision.author=…` (`help docx run` for the full `revision.*` set — `format`/`moveFrom`/`moveTo` too). Add a comment: `add \"/body/p[4]\" --type comment --prop author=… --prop text=…`; reply-thread it with `--prop parentId=N` and mark it resolved with `set \"/comments/comment[N]\" --prop done=true` (resolve rather than delete to keep the audit trail — `query 'comment[done=false]'` then lists what's still open). Prop schema: `help docx comment` / `help docx run`.\n\n**Watermark.** `add / --type watermark --prop text=\"DRAFT\" --prop color=BFBFBF --prop opacity=0.8` in one command (default opacity 0.5); `set /watermark --prop opacity=…` adjusts it later.\n\n**When to switch skills.** Stay in docx for chapter drafts, ≤ 3 footnotes, ≤ 2 equations, no bibliography/cross-refs. Switch to **`academic-paper`** for citation styles (APA / Chicago / IEEE / GB 7714), in-text↔reference auto-linking, numbered equations with `\\ref`, \"List of Figures\", or auto-updating cross-refs. Switch to **`officecli-word-form`** when the document's purpose is **data capture** — fillable forms, contracts with user-fill slots, questionnaires, mail-merge templates (`<w:sdt>` content controls, `<w:ffData>`, `documentProtection=forms`).\n\n### Raw-set escape hatch (L1 / L2 / L3)\n\nThree tiers of precision; use the lowest that does the job.\n\n- **L1 — high-level props** (`--prop text=…`, `--prop style=Heading1`): your default. Covers 80%.\n- **L2 — dotted-attr fallback** (`pbdr.top=`, `ind.left=`, `shd.fill=`, `padding.top=`, `font.size=`): when L1 lacks the knob. Example: `--prop pbdr.bottom=\"single;6;1F4E79;0\"`. Emits schema-valid XML.\n- **L3 — `raw-set` with XML**: last resort, no schema protection. Use for internal hyperlinks, composite fields, and other shapes the typed verbs can't express (see XML appendix).\n\nBorders use the format `style;size;color;space`: `single;4;FF0000;1`. Hex colors never start with `#`: `FF0000`. Scheme color names (`accent1..6`, `dark1`/`dark2`, `light1`/`light2`, `hyperlink`) are accepted anywhere a hex color is — prefer hex for stable colors across themes.\n\n## QA (Required)\n\n**Assume there are problems — QA is a bug hunt, not a confirmation step.** Your first document is almost never correct; zero issues on first inspection means you weren't looking hard enough. Headings look fine until `view outline` shows an H3 directly under an H1; the footer shows \"Page 1\" until `get --depth 3` reveals a static run, not a field.\n\n### Minimum cycle before \"done\"\n\n1. `officecli view \"$FILE\" issues` — empty paras, missing alt text, formatting anomalies.\n2. `officecli view \"$FILE\" outline` — heading hierarchy (no H1 → H3 skips), TOC presence, section count.\n3. `officecli view \"$FILE\" text --max-lines 400` — typos, stray `\\$`/`\\t`/`\\n` literals, placeholder tokens.\n4. `officecli validate \"$FILE\"` — schema check (the Delivery Gate re-runs this on the closed, on-disk file).\n5. **Visual pass — whole document as a contact sheet** (vision-capable agents only — if you cannot interpret images, skip this step: steps 1–4 are your ceiling, and flag the document \"not visually verified\" at handoff). `officecli view \"$FILE\" screenshot --grid auto -o /tmp/sheet.png`, then Read it. `--grid auto` tiles **every page** into one image (auto column count; `--grid 4` to force) — you *see* pagination, blank pages, heading rhythm, lopsided margins, and TOC/cover placement, not just the DOM. Windows+Word renders each page through real Word; elsewhere HTML. If the screenshot fails, fall back to `view html` and flag cross-page breaks / alignment / rhythm as \"not visually verified\". Thumbnails only **locate**: confirm any fine call (column alignment, line spacing, indents, dark-on-dark, caption placement) on the suspect page at full resolution with `screenshot --page N` (no `--grid`; real Word on Windows). \"validate pass\" is not delivery; \"looks like a real document\" is.\n6. If anything failed, fix, then **rerun the full cycle** — one fix commonly creates another problem.\n\n### Delivery Gate (run before handing off — any failure = REJECT, do NOT deliver)\n\nCopy-paste, set `FILE`, and refuse to declare done until every gate prints OK.\n\n```bash\nFILE=\"your-file.docx\"\n\n# Gate 1 — schema.\nofficecli close \"$FILE\" 2>/dev/null\nofficecli validate \"$FILE\" | grep -q \"no errors found\" || { echo \"REJECT Gate 1: validate failed\"; exit 1; }\necho \"Gate 1 OK\"\n\n# Gate 2 — token leak (shell-escape / template tokens / literal \\$ \\t \\n). grep -c never false-PASSes.\nLEAK=$(officecli view \"$FILE\" text | grep -cE '(\\$[A-Za-z_]+\\$|\\{\\{[^}]+\\}\\}|<TODO>|xxxx|lorem|\\\\[\\$tn])')\n[ \"$LEAK\" -eq 0 ] && echo \"Gate 2 OK\" || { echo \"REJECT Gate 2: $LEAK leak line(s)\"; officecli view \"$FILE\" text | grep -nE '(\\$[A-Za-z_]+\\$|\\{\\{[^}]+\\}\\}|<TODO>|xxxx|lorem|\\\\[\\$tn])'; exit 1; }\n# A TOC placeholder is valid before a Word-compatible field engine updates it; confirm the TOC field and updateFields setting structurally instead.\n\n# Gate 3 — live PAGE field exists when a footer is expected.\nFLD=$(officecli query \"$FILE\" 'field[fieldType=page]' --json | jq '.data.results | length')\n[ \"$FLD\" -ge 1 ] && echo \"Gate 3 OK\" || { echo \"REJECT Gate 3: no live PAGE field\"; exit 1; }\necho \"Delivery Gate PASS\"\n```\n\n### Field / cached-value spot-check\n\nFields carry cached values that may be stale or empty at write time — confirm existence by **structure, not text**.\n\n- **Footer PAGE:** `get /footer[N] --depth 3` lists the begin / instrText / separate / cached / end run chain — ≥ 5 runs for one PAGE, ≥ 11 for composite \"Page X of Y\". A single run with text `\"Page\"` = field missing; re-add with `--prop field=page`.\n- **TOC:** `get /toc[1] --depth 2` shows field structure. Page numbers may read `1 1 1 1` or `Update field to see…` until recalculated (see §Table of Contents — set `updateFields=true`).\n- **MERGEFIELD:** `query 'field[fieldType=mergefield]'` — one per slot, no literal `{{name}}` elsewhere.\n\n### Honest limit\n\n`validate` catches schema errors, not design errors — a document can pass it with wrong heading hierarchy, fake-Heading-1 sizes, placeholder tokens as body text, or an empty first-page footer on a coverless document. The contact-sheet visual pass (`screenshot --grid`) and the field-structure check are how you catch what validation can't.\n\n### QA display notes (don't chase these)\n\n- `view text` shows `\"1.\"` for every numbered list item regardless of rendered number — actual output increments correctly.\n- `view issues` flags \"body paragraph missing first-line indent\" on cover paragraphs, centered headings, list items, bibliography entries — first-line indent is only required for APA/academic body text; on block-style professional documents these are expected.\n\n## Known Issues & Pitfalls\n\nWhen something \"looks broken\", attribute it before chasing: **[AGENT-ERROR]** the document is wrong (fix it) · **[RENDERER-BUG]** the document is correct, a viewer renders it differently (don't chase) · **[SKILL gap]** the skill didn't teach the rule (file an issue).\n\n### Renderer quirks (cross-viewer, [RENDERER-BUG] — don't chase)\n\nBefore calling a color/field/chart broken, open the file in the user's target viewer; if it looks correct there it's a viewer quirk.\n\n- **PAGE field may render literal \"Page\"** (no number) until recalculated — judge by `fldChar` presence, not the digit.\n- **TOC cached page numbers may read \"1 1 1 1\"** until F9.\n- **Pie / doughnut fill may collapse to one color** in some viewers (column/bar render fine).\n- **Form-control checkboxes may render double-boxed**; **OMML equation baselines** may shift across viewers (XML identical).\n\n### Common pitfalls\n\n| Pitfall | Correct approach |\n|---|---|\n| `--index` vs `[N]` | `--index` is 0-based; `[N]` paths are 1-based |\n| Multiple `add --index N` with the same N | Each insert shifts later content down; reusing N puts later items BEFORE earlier ones — insert in reverse order, or `move --after/--before` anchored on `paraId` |\n| Unquoted `[N]` in zsh/bash | Quote every path: `\"/body/p[1]\"` |\n| `[last]` as predicate | Must be `[last()]` with parens |\n| Raw twips in spacing | Use unit-qualified values: `12pt`, `0.5cm`, `1.5x` |\n| Empty paragraphs for spacing | Use `spaceBefore` / `spaceAfter` |\n| Row-level `set` for cell formatting | Row `set` only supports `height`, `header`, `c1..cN` text; format goes on the cell paragraph / run |\n| `listStyle` on a run | It's a paragraph property |\n| Indent via leading spaces | `indent=720` / `firstLineIndent=360` / `hangingIndent=720` (dotted `ind.left` / `ind.firstLine` also work) |\n| Cover page-number suppression via `set differentFirstPage=true` | UNSUPPORTED — add a first-type footer: `--type footer --prop type=first --prop text=\"\"` |\n| Need a fresh chapter page | Use `pageBreakBefore=true` on the heading; use an explicit `pagebreak` only as an alternative, never both |\n| Multiple bullet paragraphs in one cell | `c1=\"a\\nb\"` makes a `<w:br/>` line break (one paragraph); for separate bullet paragraphs use recipe (e) |\n| `raw-set` when dotted-attr would work | Prefer L2 dotted-attr over L3 raw-set |\n| Next paragraph inherits the previous Heading style | Set explicit `--prop style=Normal` on the following paragraph |\n| Modifying a file open in Word | Close it in Word first |\n| Echo into batch breaks on `$`/`'` | Heredoc with single-quoted delimiter: `cat <<'EOF' \\| officecli batch …` |\n\n## Raw-set XML appendix (L3 patterns)\n\n`raw-set` injects literal OOXML — no schema protection. Element order in `<w:pPr>`: `pStyle`, `numPr`, `spacing`, `ind`, `jc`, `rPr` (last). Smart quotes as entities (`&#x2018;`/`&#x2019;`/`&#x201C;`/`&#x201D;`). Add `xml:space=\"preserve\"` to any `<w:t>` with leading/trailing spaces. RSIDs are 8-digit hex. Use \"Claude\" as the author for tracked changes/comments unless the user names another.\n\n**Tracked-change insertion / deletion** — prefer the high-level `--prop revision.type=ins|del` on a run; raw-set only for what the typed path can't express (rejecting/restoring another author's change, below). Replace the whole `<w:r>…</w:r>`, never inject tags inside a run; copy the original `<w:rPr>` into both to preserve formatting. Inside `<w:del>` use `<w:delText>` (and `<w:delInstrText>` for instructions):\n\n```xml\n<w:r><w:t>The term is </w:t></w:r>\n<w:del w:id=\"1\" w:author=\"Claude\" w:date=\"2026-01-01T00:00:00Z\"><w:r><w:delText>30</w:delText></w:r></w:del>\n<w:ins w:id=\"2\" w:author=\"Claude\" w:date=\"2026-01-01T00:00:00Z\"><w:r><w:t>60</w:t></w:r></w:ins>\n<w:r><w:t> days.</w:t></w:r>\n```\n\nWhen deleting ALL content of a paragraph/list item, also mark the paragraph mark deleted (`<w:del/>` inside `<w:pPr><w:rPr>`) — otherwise accepting changes leaves an empty paragraph. To **reject another author's insertion**, nest your `<w:del>` inside their `<w:ins>`; to **restore their deletion**, add a `<w:ins>` after it (don't modify theirs).\n\n**Internal hyperlink to a bookmark** (prefer the high-level `--prop anchor=` path; raw-set only for custom run styling the command can't express):\n\n```xml\n<w:hyperlink w:anchor=\"chapter1\"><w:r><w:rPr><w:rStyle w:val=\"Hyperlink\"/></w:rPr><w:t>See Chapter 1</w:t></w:r></w:hyperlink>\n```\n\n**Composite field in one run** (e.g. two fields the single-command path can't compose) — the `fldChar begin / instrText / separate / value / end` chain:\n\n```xml\n<w:r><w:fldChar w:fldCharType=\"begin\"/></w:r>\n<w:r><w:instrText xml:space=\"preserve\"> PAGE </w:instrText></w:r>\n<w:r><w:fldChar w:fldCharType=\"separate\"/></w:r>\n<w:r><w:t>1</w:t></w:r>\n<w:r><w:fldChar w:fldCharType=\"end\"/></w:r>\n```\n\n**Comment markers** are siblings of `<w:r>`, NEVER inside one (reply threading and resolved-state are high-level — `--prop parentId=`/`done=`, see Comments above):\n\n```xml\n<w:commentRangeStart w:id=\"0\"/><w:r><w:t>annotated text</w:t></w:r><w:commentRangeEnd w:id=\"0\"/>\n<w:r><w:rPr><w:rStyle w:val=\"CommentReference\"/></w:rPr><w:commentReference w:id=\"0\"/></w:r>\n```\n\nForce field recalc on open with `officecli set \"$FILE\" /settings --prop updateFields=true` (writes `<w:updateFields w:val=\"true\"/>`; covers the layout-dependent fields PAGE / PAGEREF / NUMPAGES / TOC page numbers — no raw-set needed). For SEQ numbering, prefer `set / --prop recalcFields=seq`, which writes correct cached values now without waiting on Word.\n\n### Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <verb> <element>`, `--json` for agents. Help is the authoritative schema; this skill is the decision guide.\n","frontmatter":{"name":"officecli-docx","description":"Use this skill any time a .docx file is involved -- as input, output, or both. This includes: creating Word documents, reports, letters, memos, or proposals; reading, parsing, or extracting text from any .docx file; editing, modifying, or updating existing documents; working with templates, tracked changes, comments, headers/footers, or tables of contents. Trigger whenever the user mentions 'Word doc', 'document', 'report', 'letter', 'memo', or references a .docx filename."},"isInternal":false,"tokens":11693,"sizeBytes":43199},{"name":"SKILL.md","path":"skills/officecli-financial-model/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-financial-model/SKILL.md","title":"officecli-financial-model","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-financial-model\ndescription: \"Use this skill when the user wants to build a financial model — 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising projections — in Excel. Trigger on: 'financial model', '3-statement model', 'P&L + BS + CF', 'DCF', 'WACC', 'NPV', 'terminal value', 'LBO', 'debt schedule', 'cash sweep', 'MOIC', 'IRR / XIRR', 'sensitivity table', 'scenario analysis', 'ARR model', 'unit economics', 'CAC / LTV', 'cap table forecast'. Output is a single formula-driven .xlsx. This skill is a scene layer on top of officecli-xlsx — it inherits every xlsx v2 rule (4-color code, visual floor, number formats, cache-drift, Known Issues, Delivery Gate minimum cycle). DO NOT invoke for a simple budget tracker, CSV dump, or operational KPI sheet — route those to officecli-xlsx base.\"\n---\n\n# OfficeCLI Financial-Model Skill\n\n**This skill is a scene layer on top of `officecli-xlsx`.** Every xlsx hard rule — shell quoting, incremental execution, Help-First Rule, visual delivery floor, CFO 4-color code (blue input / black formula / green cross-sheet / yellow-fill assumption), number-format standards (years as text, zero as `-`, `%` one decimal, negatives in parens), assumption-cell discipline, CSV batch import, chart data-feed forms (a/b/c), the 5-gate Delivery cycle, cache-drift guidance, Known Issues (the cross-sheet `!` trap, batch + resident for formulas, renderer caveats) — is **inherited, not re-taught**. This file adds only what a **financial model** requires on top: three-zone architecture, 3 model-type recipes (3-statement / DCF / LBO), sensitivity + scenario protocols, financial-function patterns, circular-reference discipline, and model-specific Delivery Gates 4–6.\n\nWhen the xlsx base rules cover it, the text here says `→ see xlsx v2 §X`. Read `skills/officecli-xlsx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Help-First Rule\n\nThis skill teaches what a financial model requires, not every CLI flag. When a prop name / alias / enum is uncertain, consult help BEFORE guessing: `officecli help xlsx [element] [--json]`. Help is authoritative for the installed version — when this skill and help disagree, **help wins**. Every `--prop X=` below was verified against `officecli help xlsx <element>`.\n\n## Mental Model & Inheritance\n\n**Inherits xlsx v2.** Read `skills/officecli-xlsx/SKILL.md` first. This skill assumes you know `create` / `open` / `close`, `set` values/formulas, `batch` heredocs for cross-sheet formulas, `/SheetName/A1` paths, named ranges, the 5-gate Delivery cycle, the cross-sheet `!` trap, and that **cross-sheet formulas go non-resident (single batch OR individual `set`), never batch-while-resident**.\n\n## Shell & Execution Discipline\n\nShell quoting, incremental execution, `$FILE` convention → see xlsx v2 §Shell & Execution Discipline. Same rules: quote every `[N]` path, single-quote any prop containing `$` (every number format here — `$#,##0;($#,##0);\"-\"` — needs single quotes), no hand-written `\\$`/`\\t`/`\\n`, one command at a time. Examples below use `$FILE` (`FILE=\"model.xlsx\"`).\n\n## Core Principles (identity)\n\nA financial model is an xlsx with a **decision-grade, formula-driven layer**: every output traces an unbroken chain to blue-font assumptions, every statement balances every period, every valuation is re-auditable. Eight deltas on top of a general xlsx:\n\n1. **Three-zone architecture mandatory:** Inputs → Calc → Outputs. Collapsing zones → unauditable.\n2. **Assumptions live in cells, never inside formulas.** `=B5*(1+Assumptions!GrowthRate)`, never `=B5*1.05`.\n3. **Statements balance every period.** `Assets − Liab − Equity = 0`, `CF.EndingCash = BS.Cash`. Gate 4 fails on `IMBALANCED`.\n4. **Hardcodes audited.** Calc sheets carry zero hardcoded numbers; Gate 6 counts.\n5. **Sensitivity / scenario is first-class.** 2-axis grid, dropdown `INDEX/MATCH` switch, or Base/Upside/Downside cols. Excel Data Tables not reliably supported — manual grids only.\n6. **Cached values on valuation cells load-bearing.** A valuation cell that ships with no cached result (or the `#OCLI_NOTEVAL!` sentinel) sends a blank/wrong number to non-recalculating readers. The evaluator now computes NPV / XNPV / IRR / XIRR; verify the cached value with a readback. Gate 5 spot-checks.\n7. **Circularity is a design choice.** Legitimate rings (interest ↔ cash, revolver plug ↔ ending cash) use `calc.iterate=true`. Accidental circularity is broken algebra — never papered with `iterate`.\n8. **Named ranges for ≥ 3-use assumptions.** `WACC`, `TaxRate`, `TerminalGrowth`, `ExitMultiple`, `ChurnRate`. Declared-unused names are dead decoration — Gate 6 flags.\n\n### Reverse handoff — when to go BACK to xlsx base\n\nStay in **xlsx base** for: budget trackers, CSV-to-report dumps, operational KPI sheets, simple templates, cap tables without forecast logic. Use **this skill** only when the ask mentions: 3-statement / DCF / WACC / NPV / TV / LBO / debt schedule / MOIC / IRR / unit economics / ARR roll-forward / sensitivity grid / scenario switch / pro forma.\n\n## Three-zone architecture (hard rule)\n\nEvery model in this skill builds on three zones. **Name them, tab-color them, and enforce them with executable audits.** Breaking the zone rule is the single most common cause of an unauditable model.\n\n| Zone | Sheet names (convention) | Tab color | Content | Hardcodes | Formulas |\n|---|---|---|---|---|---|\n| **Inputs** | `Assumptions`, `Inputs`, `Drivers` | Yellow `FFC000` | Raw drivers: growth rates, margins, tax, WACC, FTE, pricing, working-capital days | Blue `0000FF` on every cell | Allowed only for derived assumptions (e.g. `=MonthlyARPU*12`) |\n| **Calc** | `P&L`, `Balance Sheet`, `Cash Flow`, `DCF`, `Debt`, `ARR` | Blue `4472C4` | All derivations and statements | **Zero** (enforced by Gate 6) | Black `000000` for same-sheet, green `008000` for cross-sheet |\n| **Outputs** | `Summary`, `Dashboard`, `Sensitivity`, `Returns` | Green `70AD47` | KPIs, sensitivity grids, charts, returns waterfall | Only for labels (non-numeric); Gate 6 counts numeric hardcodes → 0 | Black / green per above |\n\n**Build order is cross-zone-aware.** Assumptions first, then Calc bottom-up on the dependency chain (`IS → BS → CF` for 3-statement; `FCF → WACC → NPV` for DCF), then Outputs last. Building Outputs first caches `0` everywhere and downstream inherits zeros.\n\n**Executable zone audit** (run before Gate 4):\n\n```bash\n# Calc zone: zero numeric hardcodes allowed. `cell:not(:has(formula))` selects the literal (non-formula) cells; `cell:has(formula)` selects the formula cells.\nHARDCODE=$(officecli query \"$FILE\" 'cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null) | select(.path | test(\"/(P&L|Balance Sheet|Cash Flow|DCF|Debt|ARR)/\"))] | length')\n[ \"$HARDCODE\" -eq 0 ] && echo \"Zone audit OK\" || { echo \"REJECT: $HARDCODE hardcoded numeric cells on Calc sheets — move to Assumptions\"; exit 1; }\n# Assumptions zone: should be non-zero.\nINPUTS=$(officecli query \"$FILE\" '/Assumptions/cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null)] | length')\n[ \"$INPUTS\" -ge 5 ] && echo \"Assumptions has $INPUTS hardcoded drivers\" || echo \"WARN: Assumptions has only $INPUTS inputs\"\n```\n\n## Print delivery (board / IC / LP)\n\nWhen the ask contains \"print\" / \"一页\" / \"董事会\" / \"投资人\" / \"IC memo\" / \"LP update\", the print pipeline must emit **only** the Outputs zone. Two artefacts:\n\n```bash\n# 1. Print_Area scoped to the Outputs sheet (Summary or Dashboard).\nofficecli add \"$FILE\" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Summary --prop 'refersTo=Summary!$A$1:$H$40'\n# 2. Hide every non-Outputs sheet — Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.\nfor S in Assumptions 'P&L' 'Balance Sheet' 'Cash Flow' DCF WACC Debt FCF 'S&U' Exit Returns; do\n  officecli raw-set \"$FILE\" /workbook --xpath \"//x:sheet[@name='$S']\" --action setattr --xml \"state=hidden\" || true\ndone\n# 3. fit-to-page landscape on Outputs sheet.\nofficecli raw-set \"$FILE\" /Summary --xpath \"//x:worksheet\" --action prepend --xml '<sheetPr xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><pageSetUpPr fitToPage=\"1\"/></sheetPr>'\n```\n\nDelete any `Print_Area` set on Calc sheets — conflicting scopes emit multi-page output with Assumptions / statement sheets leaking.\n\n## Build-order & cache-drift rule (critical for 3-statement)\n\nThree facts cause silent wrong numbers: (1) new formulas ship without cached values — Excel recomputes on open, HTML preview / older viewers do not; (2) downstream written in the same sequence as upstream caches `0` from upstream's pre-cache state; (3) cross-sheet `batch` while resident is open deadlocks at 3–5 ops.\n\n**Discipline (every recipe):**\n- Build order follows the data chain: `P&L → BS → CF` (3-statement); `FCF → WACC → NPV → Sensitivity` (DCF); `S&U → Debt → P&L → CF → Returns` (LBO).\n- After the cross-sheet chain, **cache-refresh pass:** re-issue `set` on every summary / valuation / balance-check cell, non-resident.\n- Spot-check: `officecli get \"$FILE\" /Summary/B2 --json | jq '.data.results[0].format.cachedValue'` returns a plausible non-null value. `null` means Excel will compute on open (OK for delivery). If a cell shows the `#OCLI_NOTEVAL!` sentinel: close residents, re-set; still unevaluated → cache-fallback (§Financial function patterns).\n\n## Recipes — three model types\n\nEach recipe below is **runnable skeleton, not finance theory**. Substitute numbers; don't restructure. All recipes assume `FILE=\"model.xlsx\"` is set and you have run `officecli create \"$FILE\"` + `officecli open \"$FILE\"`. Close with `officecli close \"$FILE\"` at the end.\n\n### Recipe A — 3-statement model (P&L + BS + CF)\n\n**What this recipe produces.** 4 sheets: `Assumptions`, `P&L`, `Balance Sheet`, `Cash Flow`, plus `Summary`. Year columns 2024A · 2025E · 2026E · 2027E. Balance-check row on BS; cash-reconciliation row on CF. Every statement row = formula → Assumptions.\n\n**Build order (MANDATORY).** `Assumptions → P&L → Balance Sheet → Cash Flow → Summary`. Do NOT build BS before P&L — `RetainedEarnings` depends on `NI`. Do NOT build CF before BS — `CF.OpeningCash = prior period CF.EndingCash` self-chain requires BS cash anchored for Y1. The skill's Gate 4 balance check fails silently if order is wrong.\n\n**Step 1 — sheets + tab colors + freeze panes.**\n\n```bash\nofficecli add \"$FILE\" / --type sheet --prop name=Assumptions --prop tabColor=FFC000\nofficecli add \"$FILE\" / --type sheet --prop name=P&L --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name='Balance Sheet' --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name='Cash Flow' --prop tabColor=4472C4\nofficecli add \"$FILE\" / --type sheet --prop name=Summary --prop tabColor=70AD47\nofficecli set \"$FILE\" /Assumptions --prop freeze=B2\nofficecli set \"$FILE\" /P&L --prop freeze=B3\nofficecli set \"$FILE\" \"/Balance Sheet\" --prop freeze=B3\nofficecli set \"$FILE\" \"/Cash Flow\" --prop freeze=B3\n```\n\n**Step 2 — assumptions (blue, yellow-fill on key drivers).** Year headers row 2, labels down col A, blue numeric inputs on B:E. Drivers: `RevenueGrowth`, `GrossMargin`, `OpExRatio`, `TaxRate`, `DaysReceivable/Inventory/Payable`, `CapExRatio`, `DepreciationYears`. `font.color=0000FF` on B:E. Yellow-fill (`fill=FFFF00`) the 3–5 scenario-switched drivers.\n\n**Declare named ranges for ≥3-use drivers and reference them** (`StartingARR`, `TaxRate`, `OpeningCash`, `GrowthRate`, `GrossMargin`). Formulas: `=StartingARR` not `=Assumptions!B4`; `=EBT*TaxRate` not `=EBT*Assumptions!B8`. Declared-unused names = dead decoration, Gate 6 rejects.\n\n**Step 3 — P&L rows (all formulas).** Rows: `Revenue` / `COGS` / `Gross Profit` / `OpEx` / `EBITDA` / `D&A` / `EBIT` / `Interest` / `EBT` / `Tax` / `Net Income`. Every row = formula referencing `Assumptions` or prior-row cells. Example revenue-side block — **substitute your row numbers**. Row-map for this example: `B3=Revenue, B4=COGS, B5=Gross Profit, B7=OpEx, B9=EBITDA, B10=EBIT, B15=Net Income`. Submit as single non-resident batch:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/P&L/B3\",\"props\":{\"formula\":\"Assumptions!B5\",\"font.color\":\"008000\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/C3\",\"props\":{\"formula\":\"B3*(1+Assumptions!C6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/D3\",\"props\":{\"formula\":\"C3*(1+Assumptions!D6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/E3\",\"props\":{\"formula\":\"D3*(1+Assumptions!E6)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/B4\",\"props\":{\"formula\":\"-B3*(1-Assumptions!B7)\"}},\n  {\"command\":\"set\",\"path\":\"/P&L/B5\",\"props\":{\"formula\":\"B3+B4\"}}\n]\nEOF\n```\n\nAssumptions refs (`B5`, `C6`, `B7`) are also placeholder rows — better: **define named ranges** for each driver (Step 2) so formulas read `=StartingRevenue*(1+RevenueGrowth_Y2)` regardless of row layout. Repeat for `OpEx` / `D&A` / `Interest` / `Tax` / `NI`. `font.color=008000` on every cross-sheet-reference cell; same-sheet cells default `000000`. `numFmt='$#,##0;($#,##0);\"-\"'` on all $ rows.\n\n**Step 4 — Balance Sheet rows (all formulas).** Assets = `Cash + AR + Inventory + Net PP&E`. Liab = `AP + Debt`. Equity = `OpeningEquity + RetainedEarnings`. Working-capital rows use Days assumptions: `AR = Revenue × DaysReceivable / 365`. `Net PP&E` rolls forward: `Beg + CapEx − Depreciation`. **`BS.Cash` is NOT an independent plug** — it MUST equal `'Cash Flow'!B<ending-cash-row>` (populated in Step 5).\n\n**Retained Earnings — live formula every period.** `BS.RE(t) = BS.RE(t-1) + 'P&L'!NI(t) − Dividends(t)`. Hardcoded RE rounds to whole dollar → BS shows ±$1 off every period (CFO reads \"model doesn't balance\"). For Y1 Historical RE (no prior NI), compute via BS identity as a **live formula**: `BS!RE_Y1 = TotalAssets − TotalLiabilities − PaidInCapital`. Blue-font + classic comment on the Y1 cell; Y2..Y5 stay NI-driven.\n\n**Step 5 — Cash Flow rows (all formulas).** Operating: `NI + D&A − ΔWorkingCapital`. Investing: `−CapEx`. Financing: `ΔDebt − Dividends`. Ending Cash = `Opening + Operating + Investing + Financing`. **Year 2+ Opening Cash = prior period Ending Cash** — self-chain on the same sheet: `C17 = B19`, `D17 = C19`, `E17 = D19`. The Y1 `OpeningCash` is an Assumptions input.\n\n**Step 6 — Balance check + cash reconciliation rows (hard delivery checks).** Row-map for this example: `Balance Sheet: B10=Total Assets, B15=Total Liab, B17=Total Equity, B18=Balance Check`; `Cash Flow: B5=BS.Cash (cross-sheet anchor), B19=CF.Ending Cash, B21=CF-BS Cash Recon`. Substitute your layout's rows — the logic is the check, not the cell addresses.\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Balance Sheet/B18\",\"props\":{\"formula\":\"IF(ABS(B10-B15-B17)<0.01,\\\"OK\\\",\\\"IMBALANCED: \\\"&ROUND(B10-B15-B17,0))\",\"bold\":\"true\",\"font.color\":\"000000\"}},\n  {\"command\":\"set\",\"path\":\"/Cash Flow/B21\",\"props\":{\"formula\":\"IF(ABS(B19-'Balance Sheet'!B5)<0.01,\\\"OK\\\",\\\"CF != BS CASH: \\\"&ROUND(B19-'Balance Sheet'!B5,0))\",\"bold\":\"true\"}}\n]\nEOF\n```\n\nReplicate across columns C/D/E. Apply red fill (`fill=FFC7CE`) conditionally via `type=containsText --prop text=IMBALANCED` or `text=\"CF !=\"`. Gate 4 queries these rows and refuses delivery on any `IMBALANCED`.\n\n**Step 7 — cache refresh + format pass.** Re-set every summary cell on `Summary`, every balance-check / recon cell, and every cross-sheet reference on BS / CF (non-resident, single batch per sheet). Apply column widths (`col[A]=28`, `col[B:E]=15`), `numberformat='$#,##0;($#,##0);\"-\"'` on all dollar rows, header fills (`fill=1F3864`, `font.color=FFFFFF`, `bold=true`) on section-header rows (REVENUE / COGS / ASSETS / LIABILITIES). Header fill must cover A:E, not just the label cell (→ xlsx v2 §visual floor).\n\n**Step 8 — Summary / Dashboard KPIs + charts.** Minimum 4 KPIs: `Revenue 27E`, `EBITDA Margin 27E`, `Ending Cash 27E`, `Net Income CAGR` — each a formula referencing a statement cell, green font.\n\n**Minimum 3 charts on any Dashboard delivered to a board / executive audience** — one chart is a draft, three is a deliverable. Pre-populate `Summary!A10:E13` with Gross Margin / EBITDA Margin / NI Margin ratio rows (formulas referencing `P&L`) before adding the margin chart.\n\n```bash\n# (1) Top-line trend (Revenue + EBITDA).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=column --prop dataRange='P&L!A2:E5' --prop title='Revenue & EBITDA' --prop width=14cm --prop height=8cm\n# (2) Margin trend (Gross / EBITDA / NI margin).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=line --prop dataRange='Summary!A10:E13' --prop title='Margin trend' --prop width=14cm --prop height=8cm\n# (3) Cash trajectory (Ending Cash ± Runway).\nofficecli add \"$FILE\" /Summary --type chart --prop chartType=area --prop dataRange='Cash Flow!A19:E19' --prop title='Ending cash' --prop width=14cm --prop height=8cm\n```\n\n**Verification (run all three):**\n\n```bash\n# Balance check every period must say OK (range get → cells under .data.results[0].children[])\nofficecli get \"$FILE\" \"/Balance Sheet/B18:E18\" --json | jq '.data.results[0].children[] | .format.cachedValue // .text'\n# Cash recon every period must say OK\nofficecli get \"$FILE\" \"/Cash Flow/B21:E21\" --json | jq '.data.results[0].children[] | .format.cachedValue // .text'\n# Summary KPIs are plausible numbers, not null\nofficecli get \"$FILE\" \"/Summary/B2:B5\" --json | jq '.data.results[0].children[].format.cachedValue'\n```\n\n### Recipe B — DCF valuation\n\n**What this recipe produces.** Sheets: `Assumptions`, `FCF` (10-year forecast), `WACC` (panel), `DCF` (NPV + TV + equity bridge), `Sensitivity` (2-axis grid). Output: `Implied Equity Value` + `Implied Per-Share`, with a `WACC × g` sensitivity.\n\n**Build order.** `Assumptions → FCF → WACC → DCF → Sensitivity`.\n\n**Step 1 — named ranges for key drivers.** DCF's readability depends on names. Every formula below uses `WACC`, `TaxRate`, `g` — not `$B$6`:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"WACC\",\"ref\":\"WACC!$B$12\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"TaxRate\",\"ref\":\"Assumptions!$B$8\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"TerminalGrowth\",\"ref\":\"Assumptions!$B$15\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"NetDebt\",\"ref\":\"Assumptions!$B$20\"}},\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"SharesOut\",\"ref\":\"Assumptions!$B$21\"}}\n]\nEOF\n```\n\n**Step 2 — FCF build (10 years).** Columns B:K = Y1..Y10. Rows: `Revenue` (from growth) / `EBIT` (revenue × margin) / `EBIT × (1 − TaxRate)` (NOPAT) / `+ D&A` / `− CapEx` / `− ΔNWC` / `= FCF`. Use Assumptions-driven ratios (`CapEx = Revenue × CapExRatio`). All cells formulas, black font, `numFmt='$#,##0;($#,##0);\"-\"'`.\n\n**Step 3 — WACC panel.** On `WACC` sheet, an 8-row panel: `Risk-free rate` / `Equity risk premium` / `Beta` / `Cost of equity` (=Rf + β×ERP) / `Pre-tax debt cost` / `After-tax debt cost` (=×(1−TaxRate)) / `Equity weight` / `Debt weight` / `WACC` (=We×Re + Wd×Rd_after_tax). Inputs blue; derived rows black.\n\n**Step 4 — Terminal value + NPV + equity bridge.** Row-map: `DCF: B/C 3=TV, 4=PV explicit FCF, 5=PV terminal, 6=EV, 7=Net Debt, 8=Equity Value, 9=Per-Share`; `FCF: row 2 = periods (1..10), row 11 = FCF, B:K = Y1..Y10`. Substitute your rows. Notes column cells use `{\"value\":\"text\"}`, never `{\"formula\":\"...\"}` — formula-style prose yields `#NAME?` on open (see callout after Recipe C Step 5). On `DCF` sheet:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/DCF/B3\",\"props\":{\"value\":\"Terminal value (Gordon growth)\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C3\",\"props\":{\"formula\":\"FCF!K11*(1+TerminalGrowth)/(WACC-TerminalGrowth)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B4\",\"props\":{\"value\":\"PV of explicit-period FCF (10 yr)\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C4\",\"props\":{\"formula\":\"SUMPRODUCT(FCF!B11:K11/(1+WACC)^FCF!B2:K2)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B5\",\"props\":{\"value\":\"PV of terminal value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C5\",\"props\":{\"formula\":\"C3/(1+WACC)^10\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B6\",\"props\":{\"value\":\"Enterprise value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C6\",\"props\":{\"formula\":\"C4+C5\",\"bold\":\"true\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B7\",\"props\":{\"value\":\"Less: Net debt\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C7\",\"props\":{\"formula\":\"-NetDebt\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B8\",\"props\":{\"value\":\"Equity value\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C8\",\"props\":{\"formula\":\"C6+C7\",\"bold\":\"true\",\"font.color\":\"000000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/B9\",\"props\":{\"value\":\"Implied per-share\"}},\n  {\"command\":\"set\",\"path\":\"/DCF/C9\",\"props\":{\"formula\":\"C8/SharesOut\",\"bold\":\"true\",\"numberformat\":\"$0.00\"}}\n]\nEOF\n```\n\n**`NPV` vs `SUMPRODUCT` — both cache correctly.** The evaluator computes `NPV(rate, cross_sheet_range)` and caches the right value (verify with a readback). `SUMPRODUCT(values/(1+rate)^periods)` is the algebraic equivalent (period row `FCF!B2:K2 = 1..10` is a one-time setup); it is shown here as an OPTIONAL audit-readability choice — the explicit discounting series is sometimes easier for a reviewer to trace, not a cache workaround. Either form is fine. For irregular dates, `XNPV(rate, values, dates)` likewise caches; the SUMPRODUCT equivalent is `SUMPRODUCT(values/(1+rate)^((dates-base_date)/365))`.\n\n**Step 5 — 2-axis sensitivity grid (WACC × g).** 5×5 grid. Rows = WACC values `7.5% ... 11.5%`, cols = `g` values `1.5% ... 3.5%`. Each cell = one self-contained formula re-running the DCF with the grid's WACC and g substituted. Template:\n\n```bash\n# Cell D14 (first data cell, grid anchor at C14 = WACC label, C15 = first WACC value)\n# Substitute $D$13 (this cell's g) and $C15 (this cell's WACC) into a replicated EV + equity formula.\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Sensitivity/D15\",\"props\":{\"formula\":\"(NPV($C15,FCF!$B$11:$K$11)+(FCF!$K$11*(1+D$14)/($C15-D$14))/(1+$C15)^10+(-NetDebt))/SharesOut\",\"numberformat\":\"$0.00\"}}\n]\nEOF\n```\n\nCopy the formula across D15:H19 (5×5 grid). Row 14 carries g values (blue input); column C carries WACC values (blue input). Row 13 and column B carry labels. Apply 3-color gradient CF for quick-read (green = upside, red = downside):\n\n```bash\nofficecli add \"$FILE\" /Sensitivity --type conditionalformatting \\\n  --prop type=colorScale --prop ref=D15:H19\n```\n\n**No Excel Data Tables.** Excel's native `/Data/Table` 2-variable table is not reliably supported via the CLI — each grid cell MUST be an explicit formula. Copy the template, do not try `Data Table` input cells.\n\n**Verification.**\n\n```bash\nofficecli get \"$FILE\" \"/DCF/C8\" --json | jq '.data.results[0].format.cachedValue'   # equity value, plausible $\nofficecli get \"$FILE\" \"/DCF/C9\" --json | jq '.data.results[0].format.cachedValue'   # per-share, in $XX.XX range\nofficecli get \"$FILE\" \"/Sensitivity/F17\" --json | jq '.data.results[0].format.cachedValue'   # grid center cell, plausible\n```\n\nIf `C8` or `C9` come back `null` or show the `#OCLI_NOTEVAL!` sentinel, re-set them (non-resident) — see §Build-order & cache-drift.\n\n### Recipe C — LBO model\n\n**What this recipe produces.** Sheets: `Assumptions`, `S&U` (Sources & Uses), `Debt` (multi-tranche schedule), `P&L` (5-yr), `CF`, `Exit` / `Returns`. Outputs: `MOIC`, `IRR`, and a 4-tier returns waterfall. LBO is the stress test — expect circular refs (interest ↔ cash), deepest cross-sheet chains, and the heaviest use of named ranges.\n\n**Build order.** `Assumptions → S&U → P&L → Debt → CF → Exit → Returns`. P&L before Debt (debt interest depends on P&L EBIT for coverage checks); Debt before CF (CF uses interest + principal amortization). Enable `calc.iterate` before Step 5.\n\n**Step 1 — Sources & Uses (balance required, every fee line itemized).**\n\n```\nUses    = Purchase_EV (EntryEBITDA × EntryMultiple) + Transaction_fees (Purchase_EV × TxnFeePct, typ 1.5–2.5%)\n        + Financing_fees ((Senior + Mezz) × FinFeePct, typ 1–3%) + Refinanced_debt\nSources = Senior_TLB + Mezz + Revolver_drawn + Sponsor_equity\n```\n\n**Sponsor equity — pick one, never both.** (a) **Stated:** `Sponsor_equity = Assumptions!SponsorEquity`, then scale senior/mezz so Sources = Uses (fees absorbed by debt, not a silent plug). (b) **Solved:** `Sponsor_equity = Uses − Senior − Mezz − Revolver − Refinanced`, label \"Sponsor Equity (solved)\", no standalone Assumptions ref. Hardcoded `SponsorEquity` PLUS a `=Uses − Senior − Mezz` plug guarantees silent fee absorption — stated $140M vs plug $194.67M = $54.67M unaccounted fees, CFO rejection on sight.\n\n```bash\n# Sources = Uses hard check.\nofficecli set \"$FILE\" /S&U/B12 --prop formula='IF(ABS(SUM(B4:B7)-SUM(B9:B11))<1,\"BALANCED\",\"S&U IMBALANCE: \"&ROUND(SUM(B4:B7)-SUM(B9:B11),0))' --prop bold=true\n\n# Stated-vs-plug consistency (Gate 4 addendum; only run if you chose pattern (a)).\nSTATED=$(officecli get \"$FILE\" /Assumptions/B12 --json | jq -r '.data.results[0].format.cachedValue // \"null\"')\nPLUGGED=$(officecli get \"$FILE\" /S&U/B10 --json | jq -r '.data.results[0].format.cachedValue // \"null\"')   # B10 = sponsor-equity row on S&U\nDELTA=$(python3 -c \"print(abs(float('$STATED') - float('$PLUGGED')))\" 2>/dev/null || echo 99999)\npython3 -c \"import sys; sys.exit(0 if float('$DELTA') <= 1 else 1)\" && echo \"S&U sponsor OK (stated=$STATED plug=$PLUGGED)\" || { echo \"REJECT Gate 4 S&U: stated $STATED ≠ plug $PLUGGED (Δ=$DELTA) — fees silently absorbed\"; exit 1; }\n```\n\nEvery non-sponsor line on `S&U` is a blue Assumptions input (target EBITDA, entry multiple, fee %s) or a derived formula. No hardcoded Uses / Sources numbers.\n\n**Step 2 — Debt schedule (multi-tranche).** One row per tranche per year. Columns: `BeginningBalance` / `Mandatory amortization` / `Cash sweep` / `EndingBalance` / `AverageBalance` / `InterestExpense`. Senior TLB: 1% mandatory amortization + all excess cash to sweep. Mezz: 0% amortization, interest-only cash-pay. Row-map for this example (senior TLB tranche, year 2 column C): `C4=Beginning Balance, C5=Mandatory Amort, C6=Ending Balance, C7=Cash Sweep, C8=Average Balance, C9=Interest Expense`. `CF!C20` = free cash available to sweep (year-2 ending cash pre-sweep on CF sheet). Substitute your tranche row block per layout.\n\n```bash\n# year 2 senior TLB\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Debt/C4\",\"props\":{\"formula\":\"B6\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C5\",\"props\":{\"formula\":\"-C4*Assumptions!$B$30\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C6\",\"props\":{\"formula\":\"C4+C5+C7\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C7\",\"props\":{\"formula\":\"-MIN(-CF!C20,C4+C5)\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C8\",\"props\":{\"formula\":\"(C4+C6)/2\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Debt/C9\",\"props\":{\"formula\":\"-C8*Assumptions!$B$31\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}}\n]\nEOF\n# Add the sweep-rule comment as a classic comment (comment is NOT a cell prop — separate --type comment).\nofficecli add \"$FILE\" /Debt --type comment --prop ref=C7 --prop text='cash sweep capped at available cash and remaining tranche balance'\n```\n\n**Revolver capacity cap.** If your deal uses a revolver tranche, the revolver balance each period is bounded by the commitment ceiling:\n```\nRevolver_Balance = MIN(Assumptions!RevolverCapacity, MAX(0, prior_revolver + draw − paydown))\n```\nWithout the `MIN(capacity, ...)` outer, a shortfall quarter silently over-draws the facility.\n\nAdjust row indices to your layout. Repeat for each tranche (senior / mezz / revolver) and each year.\n\n**Step 3 — P&L (5-year) + interest from Debt.** P&L interest row pulls from Debt: `Interest = 'Debt'!TotalInterestRowY<N>`. This creates the **circular reference**: Interest → NI → CF → Cash Sweep → Debt balance → Interest.\n\n**Write-order warning.** `calc.iterate=true` governs _recalculation_, not write-phase. Appending the closing leg of a cross-sheet ring to a file that already contains the ring deadlocks the engine at 100% CPU regardless of `iterate`. For complex rings (multi-tranche LBO, revolver + TLB + mezz), use §Write-order surgery below (de-ring → write downstream → re-ring). Enable `calc.iterate=true` BEFORE writing ring formulas:\n\n```bash\nofficecli set \"$FILE\" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001\n```\n\n`iterate` converges via successive approximation for naturally-dampening loops (higher interest → less cash → less sweep → higher balance, bounded by EBIT). `#REF!` or divergent values = pause; fix algebra, do not raise `iterateCount` to 1000.\n\n**Step 4 — CF + cash sweep.** Ending cash = Opening + CFO − CapEx − Mandatory amort − Cash sweep. Cash sweep = `MIN(freeCashAfterCapEx, seniorDebtBalance + seniorMandatoryAmort)`. The `MIN` cap prevents swept-below-zero.\n\n**Step 5 — Exit + Returns.** Row-map: `Exit: B3=Exit EV, B4=Less: remaining debt, B5=Exit equity to sponsor`; `Returns: B3=MOIC, B4=IRR`.\n\n```bash\n# Values/formulas — single non-resident batch.\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"set\",\"path\":\"/Exit/B3\",\"props\":{\"formula\":\"'P&L'!F8*Assumptions!$B$25\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Exit/B4\",\"props\":{\"formula\":\"-('Debt'!F6+'Debt'!F13)\",\"font.color\":\"008000\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Exit/B5\",\"props\":{\"formula\":\"B3+B4\",\"bold\":\"true\",\"numberformat\":\"$#,##0;($#,##0);\\\"-\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Returns/B3\",\"props\":{\"formula\":\"'Exit'!B5/('S&U'!B9)\",\"numberformat\":\"0.00\\\"x\\\"\"}},\n  {\"command\":\"set\",\"path\":\"/Returns/B4\",\"props\":{\"formula\":\"IRR({-'S&U'!B9,0,0,0,0,'Exit'!B5})\",\"numberformat\":\"0.0%\"}}\n]\nEOF\n# Classic comments — one --type comment per anchor cell.\nofficecli add \"$FILE\" /Exit --type comment --prop ref=B3 --prop text='Exit EV = Y5 EBITDA × exit multiple'\nofficecli add \"$FILE\" /Returns --type comment --prop ref=B3 --prop text='MOIC = exit equity / sponsor equity'\nofficecli add \"$FILE\" /Returns --type comment --prop ref=B4 --prop text='IRR — 5-yr, entry + exit only; use XIRR for mid-year dividends'\n```\n\n**Callout — labels: `comment` element vs Notes column vs `formula` (three distinct mechanics).**\n- **Hover tooltip** → `officecli add ... --type comment --prop ref=<cell> --prop text='...'`. The **`comment` key is NOT a valid prop on `set cell`** (not in `officecli help xlsx cell`) — it silently drops when embedded inside a `set cell` props dict. Use the dedicated element.\n- **Visible text in an adjacent Notes column** → `{\"command\":\"set\",\"path\":\"/DCF/D3\",\"props\":{\"value\":\"TV = FCF × (1+g) / (WACC−g)\"}}` — **`value`, not `formula`**, plain quoted string.\n- **Formula-style prose written as a real formula** → NEVER. `{\"formula\":\"FCF10*(1+g)/(WACC-g)\"}` produces `#NAME?` in Excel (`FCF10`, `g`, `WACC` are unbound identifiers in that cell context).\n\nFor mid-year dividends or partial exits, use `XIRR({cashflows}, {dates})` instead of `IRR`.\n\n**Step 6 — Returns waterfall (optional, 4-tier LP/GP).** Tiers: (1) LP preferred return 8% ; (2) GP catch-up to 20% ; (3) 80/20 split above hurdle ; (4) 100% to LP on loss. Each tier is a `MAX(0, MIN(...))` clamp. See §Sensitivity & scenarios for the general grid pattern.\n\n**Verification.**\n\n```bash\nofficecli get \"$FILE\" /S&U/B12 --json | jq '.data.results[0].format.cachedValue // .data.results[0].text'   # must say BALANCED\nofficecli get \"$FILE\" /Returns/B3 --json | jq '.data.results[0].format.cachedValue'                # MOIC, expect 2.0x-4.0x typical\nofficecli get \"$FILE\" /Returns/B4 --json | jq '.data.results[0].format.cachedValue'                # IRR, expect 0.15-0.30 typical\n# Iterate converged?\nofficecli query \"$FILE\" 'cell:contains(\"#REF!\")' --json | jq '.data.results | length'   # must be 0\n```\n\n## Sensitivity & scenarios\n\n**Three patterns, pick one:**\n- **(a) Base / Upside / Downside columns** on Assumptions — side-by-side scenarios, dropdown-less switch via an \"Active\" column + `INDEX/MATCH`.\n- **(b) Dropdown + `INDEX/MATCH` switch** — one validation dropdown on Summary drives every driver via `INDEX(Base:Downside, MATCH(Dropdown, ScenLabels, 0))`.\n- **(c) 2-axis sensitivity grid** — 5×5 or 7×7, one self-contained formula per cell, row/col headers are the two drivers. See Recipe B Step 5 for WACC × g.\n\nMixing (a)+(b) creates circular input (scenario picked by dropdown AND overwritten by Active column) — pick one.\n\n**Grid rule:** each cell substitutes row-driver and col-driver into a self-contained copy of the output formula. Cannot reference the `WACC` named range (that's the panel) — reference the grid's axis cell.\n\n**Dropdown scenario switch.** One `validation` dropdown on Summary drives every `Assumptions` row:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/Summary\",\"type\":\"validation\",\"props\":{\"sqref\":\"B1\",\"type\":\"list\",\"formula1\":\"Base,Upside,Downside\"}},\n  {\"command\":\"set\",\"path\":\"/Assumptions/B5\",\"props\":{\"formula\":\"INDEX(C5:E5,MATCH(Summary!$B$1,$C$4:$E$4,0))\"}}\n]\nEOF\n# If you want a hover tooltip on B5, add it separately:\nofficecli add \"$FILE\" /Assumptions --type comment --prop ref=B5 --prop text='Revenue growth — picked by Summary!B1 scenario dropdown'\n```\n\nEvery `Assumptions` driver row gets the same `INDEX/MATCH`. Base / Upside / Downside columns on C:E stay blue (hardcoded scenario inputs).\n\n**Football-field chart pattern (DCF valuation summary).** Horizontal Low→High bars for 3–5 valuation methods (DCF base, DCF bear, Trading comps, Precedent txns, LBO floor) stacked vertically. On a `Football` sheet: col A = method label, col B = Low $, col C = High $, col D = `=C−B` (width). Chart as a stacked bar with column B as an invisible first series (white/no-fill) and column D as the visible series — `dataRange=Football!A3:D7`, `chartType=bar`. Excel reads this as a floating bar per method.\n\n## Financial function patterns\n\nTerse reference — not a finance textbook. If you don't know what these do, pause and ask the user.\n\n| Function | Prefer over | Why |\n|---|---|---|\n| `XNPV(rate, values, dates)` | `NPV` | Irregular cash flow dates (M&A close mid-year, staggered tranches) |\n| `XIRR(values, dates)` | `IRR` | Irregular dates; multiple sign changes handled better |\n| `INDEX(range, MATCH(lookup, key, 0))` | `VLOOKUP` | Insert-safe (VLOOKUP breaks when a column is inserted in the source range) |\n| `IFERROR(x/y, 0)` or `IF(y=0, 0, x/y)` | bare division | Guard every `/` in a financial model — `#DIV/0!` shipped = delivery failure |\n| `MIRR(values, financeRate, reinvestRate)` | `IRR` with sign flips | When cash-flow pattern has 2+ sign changes |\n| `SUMIFS(sumRange, criteriaRange1, criterion1, ...)` | `SUMPRODUCT((...))` array | Clearer intent for conditional sums; either evaluates correctly |\n\n**Evaluator coverage — verify, don't preemptively hardcode.** The CLI evaluator computes the finance functions this skill uses — `NPV` / `XNPV` / `IRR` / `XIRR`, array-literal formulas (`IRR({...})`), and `SUMPRODUCT(1/COUNTIF(range,range))` distinct-count — and caches the correct value with `evaluated:true`. There is no `NPV→0` rewrite obligation and no distinct-count `1/N` trap. The honest rule: build the formula you mean, then verify the cached value with a readback (`get ... --json | jq '.data.results[0].format.cachedValue'`). Only if a specific cell genuinely comes back with the `#OCLI_NOTEVAL!` sentinel (formula written before its inputs, or an unsupported function) should you fall back — first re-set non-resident after `close`; if it still won't evaluate, hardcode the computed value with a blue font and a classic comment via `officecli add \"$FILE\" /Sheet --type comment --prop ref=<cell> --prop text='cached valuation; refreshes on open in Excel — do not edit'`, and disclose in delivery notes.\n\n## Circular references & iterative calc\n\n**Enable `calc.iterate` ONLY when circularity is algebraically justified:** Interest ↔ Cash (LBO revolver / cash sweep), Tax shield ↔ NI (rare — most 3-statement models compute interest before tax and avoid), Revolver plug ↔ Ending cash (corporate cash waterfall with min-cash).\n\n```bash\nofficecli set \"$FILE\" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001\n```\n\n`iterateCount=100` / `iterateDelta=0.001` are Excel defaults, fine for naturally dampening loops.\n\n### Write-order surgery (de-ring → write downstream → re-ring)\n\n`calc.iterate` controls recalc, not write-phase. Appending the closing leg of an already-wired cross-sheet ring (Debt.Interest ↔ CF.Cash ↔ Debt.CashSweep) deadlocks at 100% CPU; `view html` / `get` also hang on a non-converged ring.\n\n**3-step playbook:**\n1. **De-ring** — write Debt with the 10–20 ring cells set to literal `0` (e.g. `C7=0`, not `=-MIN(...)`). Removes the ring.\n2. **Write downstream** — build all non-circular chains (P&L, CF, Exit, Returns, Summary, grid) non-resident, one heredoc per sheet. Everything caches against the zeroed cells.\n3. **Re-ring** — close all residents, re-set each circular cell with its real formula, one `set` per cell, non-resident.\n\n**Acceptance.** `get /Debt/C7 --json | jq '.data.results[0].format.cachedValue'` returns non-zero non-null. If a cell still deadlocks, leave `=0` + classic comment `\"circular; recalculates in Excel on F9\"`, flag at delivery. Never paper over with `iterateCount=1000`.\n\n**Do NOT use `iterate` as a band-aid for `#REF!` / divergent values.** Raising `iterateCount` to 1000 hides the bug and ships a plausibly-wrong value; `validate` does not catch it. Break the loop algebraically (e.g. interest on opening balance only, not average).\n\n**Verify convergence.** Read the loop cell, bump a driving assumption and back, re-read — values must match:\n\n```bash\nV1=$(officecli get \"$FILE\" /Debt/C9 --json | jq '.data.results[0].format.cachedValue')\nofficecli set \"$FILE\" /Assumptions/B31 --prop value=0.085\nofficecli set \"$FILE\" /Assumptions/B31 --prop value=0.0845\nV2=$(officecli get \"$FILE\" /Debt/C9 --json | jq '.data.results[0].format.cachedValue')\n[ \"$V1\" = \"$V2\" ] && echo \"Iterate converged\" || echo \"WARN: drift V1=$V1 V2=$V2 — tighten iterateDelta or check algebra\"\n```\n\n## Audit & Delivery Gate\n\n**Assume there are problems.** First build is almost never correct. Run every gate below; every check must print its success line. `validate` passing is not delivery — the model can pass schema and still be wrong by a factor of 10.\n\n### Gates 1–3 — inherited from xlsx v2 verbatim\n\n→ see xlsx v2 §QA minimum cycle (Gates 1–3 cover `view issues`, error-cell query, `validate` after close). Run them first, exactly as written in xlsx v2. No financial-model-specific tweaks.\n\n### Gate 4 — statement integrity (3-statement & LBO)\n\nBalance-check and cash-reconciliation rows produced by Recipe A / C must show `OK` / `BALANCED` every period. `query` the check rows and refuse on any `IMBALANCED` / `CF !=`:\n\n```bash\nBS_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"IMBALANCED\")' --json | jq '.data.results | length')\nCF_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"CF !=\")' --json | jq '.data.results | length')\nSU_FAIL=$(officecli query \"$FILE\" 'cell:contains(\"S&U IMBALANCE\")' --json | jq '.data.results | length')\nif [ \"$BS_FAIL\" -eq 0 ] && [ \"$CF_FAIL\" -eq 0 ] && [ \"$SU_FAIL\" -eq 0 ]; then\n  echo \"Gate 4 OK (balance + recon + S&U all pass)\"\nelse\n  echo \"REJECT Gate 4: BS=$BS_FAIL CF=$CF_FAIL S&U=$SU_FAIL\"; exit 1\nfi\n```\n\nIf any fail, the model is silently wrong — fix the upstream chain before delivery. Most common cause: a cross-sheet formula stored `\\!` (shell-mangled) — run `officecli query \"$FILE\" 'cell:contains(\"\\\\\\\\!\")'` and re-enter via batch heredoc.\n\n### Gate 5 — cached-value sanity on valuation cells\n\nNPV / IRR / XIRR / equity-bridge / MOIC / summary KPI cells that ship unevaluated (`#OCLI_NOTEVAL!` sentinel, typically because the formula was written before its inputs) send a blank/wrong number to a reader who does not recalc on open. List every valuation cell and confirm a cached value is present:\n\n```bash\n# Customize the path list per recipe — this is the DCF example\nfor P in \"/DCF/C4\" \"/DCF/C5\" \"/DCF/C6\" \"/DCF/C8\" \"/DCF/C9\"; do\n  V=$(officecli get \"$FILE\" \"$P\" --json | jq -r '.data.results[0].format.cachedValue // \"null\"')\n  if [ \"$V\" = \"null\" ] || [ \"${V#\\#OCLI_NOTEVAL}\" != \"$V\" ]; then\n    echo \"REJECT Gate 5: $P unevaluated (cached=$V) — re-set after close (see §Build-order & cache-drift)\"; exit 1\n  fi\n  echo \"Gate 5 $P: cached=$V OK\"\ndone\n```\n\nFor LBO, extend the list: `/Exit/B5`, `/Returns/B3`, `/Returns/B4`. For 3-statement, extend with `/Summary/B2:B5`.\n\n### Gate 6 — hardcode / zone discipline\n\nEvery Calc sheet has zero numeric hardcodes. Executable:\n\n```bash\n# `cell:not(:has(formula))` selects the literal cells (and `cell:has(formula)` the formula cells).\nHARDCODE=$(officecli query \"$FILE\" 'cell[type=Number]' --json \\\n  | jq '[.data.results[] | select(.format.formula == null) | select(.path | test(\"/(P&L|Balance Sheet|Cash Flow|DCF|Debt|FCF|WACC|Exit|Returns)/\"))] | length')\n[ \"$HARDCODE\" -eq 0 ] && echo \"Gate 6 OK (no hardcodes on Calc sheets)\" || { echo \"REJECT Gate 6: $HARDCODE hardcoded numeric cells on Calc zone — move to Assumptions\"; exit 1; }\n\n# Named-range coverage + dead-decoration audit: ≥3 ranges declared AND each referenced by ≥1 formula.\nNR=$(officecli query \"$FILE\" namedrange --json | jq '.data.results | length')\n[ \"$NR\" -ge 3 ] && echo \"Gate 6 OK ($NR named ranges)\" || echo \"WARN Gate 6: only $NR named ranges\"\nDEAD=0\nfor NR_NAME in $(officecli query \"$FILE\" namedrange --json | jq -r '.data.results[].format.name'); do\n  # Match the formula SOURCE (`formula~=`), not the cached/displayed RESULT — `:contains` would scan\n  # the computed value and report every name as dead (false reject).\n  USES=$(officecli query \"$FILE\" \"cell[formula~=$NR_NAME]\" --json | jq '.data.results | length')\n  [ \"$USES\" -ge 1 ] && echo \"  $NR_NAME: $USES uses OK\" || { echo \"  WARN: $NR_NAME unused\"; DEAD=$((DEAD+1)); }\ndone\n[ \"$DEAD\" -eq 0 ] && echo \"Gate 6 named-range audit OK\" || { echo \"REJECT Gate 6: $DEAD dead-decoration name(s)\"; exit 1; }\n```\n\n### Gate 5b — visual audit via HTML preview (mandatory)\n\nGates 1–4/6 are grep defenses — they cannot see a rendered sheet. Run `officecli view \"$FILE\" html` and Read the returned HTML. Walk every sheet (inherits xlsx v2 visual floor):\n\n- No `###` in any numeric cell (widen column).\n- No truncated labels / section headers (widen column or `alignment.wrapText=true`).\n- No placeholder tokens (`TBD`, `{var}`, `xxxx`) — Gate 6.1 grep below.\n- Balance-check / recon rows say `OK` / `BALANCED` every period column.\n- Dashboard charts render, y-axis = 0 on ARR/revenue lines, source data matches statement sheet.\n- Sensitivity grid colors read green (upside) → red (downside) — color-scale CF applied.\n- No stale cached `0` on summary KPIs; if present, run cache-refresh pass.\n\nREJECT on any defect. **Human preview:** `officecli watch \"$FILE\"`, or open in Excel / WPS / Numbers — final colors + chart fidelity only fully render in the target viewer.\n\n### Gate 6.1 — token / placeholder sweep\n\n```bash\nLEAK=$(officecli view \"$FILE\" text | grep -niE 'TBD|\\(fill in\\)|xxxx|lorem|\\{\\{|placeholder|coming soon')\n[ -z \"$LEAK\" ] && echo \"Gate 6.1 OK (no placeholder tokens)\" || { echo \"REJECT Gate 6.1:\"; echo \"$LEAK\"; exit 1; }\n```\n\n### Honest limit\n\n`validate` catches schema errors, not finance errors. A model passes `validate` with `BS.Cash` hardcoded to force balance, an `NPV` cached at `0`, a sensitivity grid all-zero because it was built before FCF, a `#NAME?` runtime on a `P&L`-named sheet with unquoted refs. Gates 4 / 5 / 6 / 5b exist because schema-level `validate` cannot catch any of this.\n\n## Known Issues & Pitfalls\n\n→ Base pitfalls (cross-sheet `!` trap, batch JSON dotted-name rule, renderer caveats): see xlsx v2 §Known Issues & Pitfalls — all apply.\n\nFinancial-model-specific:\n\n- **AP sign on COGS.** Accounts Payable: if COGS is stored negative on the P&L, AP formula must negate — `=-COGS*DaysPayable/365`. Wrong sign inflates NWC and flips CF direction. Silent; passes `validate`.\n- **`#NAME?` not caught by `query` / `validate`.** A cross-sheet formula referencing `P&L!B3` without quoting the sheet name (because `&` is special) lands at runtime as `#NAME?`. Always write cross-sheet refs as `'P&L'!B3` — single-quote the sheet name if it contains `&`, space, `(`, `)`, etc. Gate 5b visual check is the only detection.\n- **Iterative calc silent non-convergence.** `calc.iterate=true iterateCount=100` converges at whatever the cap lands on — even if the true answer is 2× that. Always run convergence verify (§Circular references). Complex LBO rings (multi-tranche debt + sweep + tax shield) may not converge; when `cachedValue=0` on a ring cell, use §Write-order surgery.\n- **Batch-while-resident deadlock on circular writes.** Writing the closing leg of a cross-sheet ring via `batch` with a resident open deadlocks at 100% CPU. Even single `set` on a ring cell can hang. Fix: close residents, write the ring in two passes per §Write-order surgery. Non-resident single-heredoc is the only safe form.\n- **Cross-sheet cached value stale in `view html`.** Downstream written in the same sequence as upstream caches `0`. Excel resolves on open; HTML preview does NOT. Re-set every downstream non-resident after the chain (§Build-order & cache-drift).\n- **`NPV()` / `XNPV()` evaluate and cache correctly.** The evaluator computes both (same-sheet and cross-sheet); no rewrite is required. `SUMPRODUCT(values/(1+rate)^periods)` remains an optional audit-readability alternative, not a cache workaround.\n- **Sensitivity-grid build order still matters.** A grid cell written before its FCF/WACC inputs exist evaluates against empty inputs and may cache a wrong/blank value. Build FCF + WACC + DCF first, then the grid in a separate non-resident batch, and verify (`jq '.data.results[0].format.cachedValue'`). This is an ordering discipline, not an evaluator limitation.\n- **`BS.Cash` = CF ending cash always** (including Y1: `BS.Cash = 'Cash Flow'!B19`). Never an independent plug or Assumptions ref — a plugged `BS.Cash` hides balance errors.\n- **Year 2+ `Opening Cash` = prior period `Ending Cash`** (`C17=B19`, `D17=C19`). Independent Y2+ opening-cash inputs silently drift from BS.\n- **Waterfall chart \"total\" bars.** `chartType=waterfall` cannot mark total programmatically — use `colors=` convention (dark = total, medium = positive, red = negative). See `help xlsx chart`.\n- **DCF per-share when `SharesOut` is a formula.** `=BasicShares + OptionPool × ExerciseAssumption` → add a blue-font assumption cell and point the `SharesOut` named range at the computed cell, not the raw input.\n\n## Help pointer\n\nWhen in doubt: `officecli help xlsx [element] [--json]`. Help is the authoritative schema; this skill is the decision guide for financial-modeling deltas.\n","frontmatter":{"name":"officecli-financial-model","description":"Use this skill when the user wants to build a financial model — 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising projections — in Excel. Trigger on: 'financial model', '3-statement model', 'P&L + BS + CF', 'DCF', 'WACC', 'NPV', 'terminal value', 'LBO', 'debt schedule', 'cash sweep', 'MOIC', 'IRR / XIRR', 'sensitivity table', 'scenario analysis', 'ARR model', 'unit economics', 'CAC / LTV', 'cap table forecast'. Output is a single formula-driven .xlsx. This skill is a scene layer on top of officecli-xlsx — it inherits every xlsx v2 rule (4-color code, visual floor, number formats, cache-drift, Known Issues, Delivery Gate minimum cycle). DO NOT invoke for a simple budget tracker, CSV dump, or operational KPI sheet — route those to officecli-xlsx base."},"isInternal":false,"tokens":13788,"sizeBytes":48112},{"name":"SKILL.md","path":"skills/officecli-pitch-deck/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-pitch-deck/SKILL.md","title":"officecli-pitch-deck","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-pitch-deck\ndescription: \"Use this skill when the user is building a fundraising / investor pitch deck — seed, Series A / B / C, convertible note, SAFE round, strategic raise. Trigger on: 'pitch deck', 'investor deck', 'Series A deck', 'Series B deck', 'Series C deck', 'fundraising deck', 'seed pitch', 'VC deck', 'raising capital', 'term sheet presentation'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate). DO NOT invoke for a generic board review, sales deck, all-hands, or product launch — route those to officecli-pptx base.\"\n---\n\n# OfficeCLI Pitch Deck Skill\n\n**This skill is a scene layer on top of `officecli-pptx`.** Every pptx hard rule — visual delivery floor (title ≥ 36pt / body ≥ 18pt / title ≥ 2× body), 12-column grid on 33.87×19.05cm, 4 canonical palettes, chart-choice decision table, connector canon (`shape` / `from` / `to` / `tailEnd=triangle`), shell escape, resident + batch, Delivery Gate 1–5a — is inherited, not re-taught. This file adds only what **fundraising** needs on top: stage diagnosis (A / B / C), 5 赛道 arc templates, 10 key-slide recipes (cover / problem / solution / market / product / model / traction / team / financials / ask), pitch-specific numbers convention, a VC ship-check, and a pitch-specific fresh-eyes Gate 6.\n\nWhen the pptx base rules cover it, the text here says `→ see pptx v2 §X`. Read `skills/officecli-pptx/SKILL.md` first if you have not.\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what a fundraising deck requires, not every command flag.** When a prop name, enum value, or preset is uncertain, consult help BEFORE guessing.\n\n```bash\nofficecli help pptx                          # All pptx elements\nofficecli help pptx <element>                # Full schema (e.g. chart, shape, connector, picture)\nofficecli help pptx <element> --json         # Machine-readable\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help wins.** Every `--prop X=` in this file has been grep-verified against `officecli help pptx <element>` — if help adds / renames a prop in a later version, trust help.\n\n## Mental Model & Inheritance\n\n**Inherits pptx v2.** You should have read `skills/officecli-pptx/SKILL.md` first. This skill assumes you know how to: add slides + shapes + charts + connectors; address by `@name=` / `@id=`; quote paths; use `batch` heredocs; write `--prop tailEnd=triangle` on every flow connector; and run the 5-gate Delivery Gate. If any of those are unfamiliar, open a pptx v2 session before continuing.\n\n## Shell & Execution Discipline\n\n**Shell quoting, incremental execution, `$FILE` convention** → see pptx v2 §Shell & Execution Discipline. Same rules verbatim — quote `[N]` paths, single-quote values containing `$` (including `$35M`, `$1.2B TAM` in a cover or ask slide), never hand-write `\\$ \\t \\n` in executable examples, one command at a time. Examples below use `$FILE` (`FILE=\"deck.pptx\"`).\n\n**Single-quote every shape text containing `$`.** `--prop text=\"Series B · $35M\"` (double quotes) is WRONG — zsh expands `$35M` → empty, deck renders `Series B · M` silently. `--prop text='Series B · $35M'` (single quotes) is right. This is the #1 pitch-deck shell-escape failure mode (`$35M`, `$18M ARR`, `$1.2B TAM` appear on cover/ask/financials/milestones). Gate 2 cannot detect a stripped `$35M` — no residue. Gate 2b catches common strip patterns; single-quoting PREVENTS them.\n\n## What \"pitch deck\" means here (identity)\n\nA pitch deck is a pptx with a **fundraising layer** on top: VC-oriented narrative arc, verifiable metrics, stage-appropriate data density, founder-credibility surface. Slides are consumed at ~3 seconds per slide in a live room — the pptx v2 rule. Pitch decks add a second constraint on top: **every slide carries one investable proposition**. If a slide is \"interesting background\" that doesn't move the ask forward, cut it. VCs will not. The base pptx rules still apply; pitch decks add six deltas:\n\n1. **Stage determines everything.** Series A / B / C each dictates slide count, narrative weight, which metrics are must-haves, and tolerance for unit-econ sophistication. A Series A deck with 6 pages of CAC/LTV math reads as over-packaged; a Series B deck missing unit econ reads as incomplete. Pick the stage first — everything downstream follows.\n2. **Narrative arc beats feature dump.** 10 essential slides in a fixed order: cover → problem → solution → market → product → model → traction → team → financials → ask. Out of order = VCs disengage.\n3. **Numbers are a contract.** TAM/SAM/SOM must be clean three-layer; CAC/LTV must have a payback line; ARR ≠ revenue; Use-of-Funds must be a four-bucket pie. Sloppy numbers = round dies.\n4. **Team slide carries prior companies.** Avatar grid alone reads as a student project. Add prior-company logos / names + one-line role. Without this, first-time founders look exactly like first-time founders.\n5. **Traction chart y-axis starts at 0.** A \"hockey stick\" starting at `y_min = 80% of current` is a visual lie — VCs who have seen 10,000 decks spot it in < 2 seconds.\n6. **The ask is a slide, not a footnote.** `$XX M` hero + four-bucket Use-of-Funds + runway length. \"We're raising some money\" is not an ask.\n\n### Reverse handoff — when to go BACK to pptx base\n\nStay in **pptx v2 base** for board reviews, all-hands, sales decks, product launches, training decks — anything not tied to raising capital. Use **this skill** only when: (a) the user mentions a specific round (seed / Series A / B / C) or a VC meeting, AND (b) the deck needs at least 4 of {problem, traction, team with credentials, Use-of-Funds, stage-appropriate unit econ, financial projections}.\n\nIf the user says \"fundraising deck\" but the context is a corporate BU quarterly ask, that is a board review. Route to pptx v2 Recipe (d) 10-slide blueprint. If the user says \"board review\" but the context is a small company raising a bridge round, route here.\n\n## Series A / B / C stage diagnosis (decision tool)\n\n**Read this before writing a single command.** Pick the row that matches the user's description — everything downstream (slide count, which metrics, which recipes, what the team slide must show) derives from this one call.\n\n| Stage | Revenue band | Team | Slide count | Dominant narrative (weight) | Must-have data | Common red flag |\n|---|---|---|---|---|---|---|\n| **Seed** | $0 – $1M ARR (often pre-rev) | 2 – 8 FTE | 10 – 12 | Problem (30%) + Solution (25%) + Team (15%) + Market (15%) + Traction (15%) | Founder-market fit story; 1 – 2 design-partner / pilot logos; top-down TAM ok | Over-claiming traction (10 customers = \"market proven\") |\n| **Series A** | $1 – $5M ARR | 10 – 25 FTE | 12 – 16 | Problem (20%) + Solution (20%) + **Market \"why now\"** (15%) + Product (15%) + Traction (20%) + Team (10%) | PMF proof (NRR > 110%, low churn), bottom-up TAM/SAM, pipeline / pilots converted | Bottom-up TAM feels fabricated; CAC not yet meaningful but shown anyway |\n| **Series B** | $5 – $30M ARR | 30 – 100 FTE | 18 – 22 | **Traction + Unit econ (30%)** + Market + Product + Team + Financials (ask) | ARR curve starting at 0; NRR, CAC, LTV, payback (< 18 mo ideal); cohort retention; logo wall | No unit-econ slide; CAC payback > 24mo without explanation; Use-of-Funds missing % |\n| **Series C** | $30M+ ARR | 100+ FTE | 20 – 24 | **Financials + Scale + Moat (40%)** + Market expansion + Team depth | Multi-year GAAP, rule-of-40, GM trajectory, international expansion plan, defensibility | No moat slide; revenue growth without margin story; team slide has no prior CEO / CFO |\n| **Bridge / SAFE** | any | any | 8 – 10 | **Specific bridge reason** + runway math + commitments | Prior round context; specific milestone the bridge funds; committed investor amount | Treating a bridge like a Series A — too many slides dilutes the ask |\n\n**Decision procedure.** From one or two user sentences (\"Series B, $18M ARR, 120 customers, $35M raise\"), pick exactly one stage row. All later choices in this skill reference your stage: which 赛道 template to pull, which recipes are mandatory vs optional, and which Delivery Gate 6 checks fire.\n\n**Corner cases.** Bridge rounds & convertibles between A → B are closer to A or B depending on whether the bridge milestone is \"finish PMF\" (A shape) or \"hit unit-econ target\" (B shape). \"Extension\" rounds at the same stage reuse the earlier stage's skeleton and add a one-slide \"progress since last round\" update.\n\n**Non-SaaS stage overrides.** The ARR / unit-econ shape of Series B fits SaaS. For other verticals, substitute revenue band + unit-econ equivalent + Gate 6.3 grep:\n\n| Vertical | Revenue \"band\" at Series B | \"Unit econ\" equivalent | Gate 6.3 substitute |\n|---|---|---|---|\n| **Bio / Clinical-stage** | pre-rev, 20–60 FTE | burn rate + runway to next milestone (IND / Ph1 readout / BLA) | `shape:contains(\"ORR\")` OR `contains(\"Pipeline\")` OR `contains(\"BLA\")` OR `contains(\"runway\")` ≥ 1 |\n| **Deep Tech / Frontier** | pre-rev or early pilot rev | technical milestones + TRL level + benchmark vs SoTA | `shape:contains(\"TRL\")` OR `contains(\"benchmark\")` ≥ 1 |\n| **Marketplace / Network** | GMV $10–100M | take rate + cohort retention + liquidity | `shape:contains(\"GMV\")` + `contains(\"take rate\")` ≥ 1 |\n| **Consumer hardware** | $2–15M revenue (shipped units) | contribution margin + repeat rate + blended CAC | `shape:contains(\"repeat\")` OR `contains(\"contribution\")` ≥ 1 |\n\nSubstitute the analogue grep when running Gate 6.3 on these verticals. False WARN on SaaS CAC/LTV = expected; real concern = vertical-specific analogue present. Bio Series B decks especially: burn + runway-to-milestone IS the \"unit econ\" story.\n\n## 赛道 arc templates (5 families)\n\n5 mainstream verticals. Each one has different slide weights because what VCs require as proof-of-concept differs. Pick the vertical row; the slide skeleton is a copy-able starting point. Slide counts assume the matching stage row above.\n\n### (1) B2B SaaS / Enterprise software\n\nCanonical arc — the template most of VC muscle memory is built on. Series B example (20 slides): cover · TL;DR · problem · problem evidence · solution · product loop · market TAM/SAM/SOM · **unit economics (CAC / LTV / payback / GM)** · ARR trajectory · retention cohort · logo wall · team · competitors · financials 4-year · ask. Must-have: unit-econ slide from Series A onward; logo wall from Series B onward.\n\n### (2) Consumer (B2C app / consumer hardware / D2C)\n\nNarrative-driven. Early-stage decks lean on **product-experience screenshots + founding story + \"why now\"** market timing; lighter on unit econ (which are usually weaker than SaaS). Series A example (14 slides): cover · hook (30-second product demo or 1-line vision) · problem (lived experience) · solution (product shots) · product-experience flow · \"why now\" market window · pre-order / crowdfunding / early-sales evidence · retention / engagement (DAU, D30) · market (top-down ok if bottom-up unreliable) · competitive positioning · founder story + team · press / endorsements · financials · ask. Must-have: product visuals on ≥ 3 slides; \"why now\" slide (window justification); engagement metric not just revenue.\n\n### (3) Deep Tech / Frontier tech (AI foundation models, quantum, climate hardware, robotics)\n\nTechnology credibility is the sell. Pre-revenue deep tech replaces \"traction\" with **technical milestones + defensibility**. Series B example (22 slides): cover · thesis (one-line \"what changes if this works\") · problem (current state of art) · solution (technical approach) · **technology architecture** · benchmarks vs SoTA · pipeline / TRL levels · market (long-tail) · business model · early commercial traction (pilots, LOIs) · IP / patents · team (usually PhD / ex-FAANG-research) · partners · financials · ask. Must-have: benchmark slide; IP slide; team slide dense with PhDs / prior-lab names.\n\n### (4) Marketplace / Network business (two-sided platform, social, commerce)\n\nLiquidity is the metric. Replace \"unit econ\" with **GMV + take rate + cohort retention + supply / demand balance**. Series A example (15 slides): cover · problem (friction in current supply-demand) · solution · product demo (both sides) · network effects diagram · early liquidity (first-week GMV, time-to-match) · cohort retention · geographic / category expansion plan · competitive positioning vs incumbents · take-rate model · team · financials · ask. Must-have: liquidity metric slide; cohort retention chart; network-effect diagram.\n\n### (5) Bio / Life sciences / Healthtech\n\nRegulatory pipeline IS the business. Replace \"product roadmap\" with **clinical pipeline + regulatory path + scientific evidence**. Series B example (22 slides): cover · unmet medical need · scientific rationale (mechanism of action) · preclinical / clinical data (ORR, safety, endpoints) · **pipeline chart** (candidates × stages × dates) · differentiation vs standard of care · IP / exclusivity · regulatory strategy (IND, BTD, fast-track) · market (prevalence × pricing) · commercial strategy (orphan / specialty / biosimilar) · partnerships / collaborations · team (CSO / CMO with prior FDA wins) · financials (burn to next milestone) · ask. Must-have: pipeline chart; clinical data slide; team slide with prior regulatory wins.\n\n**Cross-vertical rule.** You can mix elements across templates, but never drop a must-have from your primary vertical. A SaaS deck missing unit econ, a bio deck missing a pipeline chart, a marketplace deck missing a liquidity metric — each is an instant VC disqualification.\n\n## Slide Patterns (layout canon)\n\nPatterns are **layout geometry**; recipes below are **narrative intent**. A slide picks one pattern for its visual shape (6 canonical ones below) and one recipe for what it argues (cover / problem / traction / ...). Multiple recipes can share one pattern — Problem / Why-Now / Traction-callout all lean on the 3-stat row (C.2). Pick the pattern first, then fill it with recipe content.\n\n**Speaker notes rule.** Every content slide (non-cover, non-closing) MUST carry speaker notes via `officecli add \"$FILE\" /slide[N] --type notes --prop text='…'`. Missing notes = not shippable — inherits pptx v2 §Hard rules (H7). Run `officecli help pptx notes` to confirm prop names before building.\n\n**Pattern reuse discipline.** Never run the same pattern on two consecutive slides — even with different data, two identical geometries in a row read as a template loop. Alternate C.2 with C.4 or C.5b to break rhythm.\n\n**Vertical centering.** When a slide carries fewer elements than the pattern's maximum, nudge y-positions down 2–3cm to center the visual weight. Tables below assume full content.\n\n### C.1 Title / Cover (dark gradient)\n\n3–4 text shapes on a gradient fill. Slide 1 in every deck.\n\n```\n+----------------------------------+\n|                                  |\n|          TITLE (centered)        |\n|          tagline                 |\n|                                  |\n|   round · amount · date          |\n|  ________________________        |  <- thin brand band\n+----------------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 2cm | 5cm | 29.87cm | 4cm | serif bold, ≥ 36pt (44 typical) |\n| Tagline | 2cm | 10cm | 29.87cm | 2cm | sans 18–22pt |\n| Meta (round · $ · date) | 2cm | 13cm | 29.87cm | 1.5cm | sans 12–16pt |\n\n**Use this when** the slide is the first one (Cover recipe 1) — 3-second identity grab. Background is a 180° linear gradient between two dark palette shades (e.g. Professional Navy `1E2761 → 0D1F35`). If the title wraps to 2 lines, **add height (4cm → 5cm), never drop font below 36pt** — sub-36pt on a pitch cover reads as timid regardless of content. Transition: fade.\n\n### C.2 3-Stat callout row\n\nTitle + 3 big-number / label pairs across. The default for Problem / Why-Now / Traction-callout slides.\n\n```\n+----------------------------------+\n|  Title                           |\n|                                  |\n|   73%      12hr      $4.2B       |\n|   label    label     label       |\n|   source   source    source      |\n+----------------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 1.5cm | 1cm | 30.87cm | 3cm | serif bold ≥ 36pt |\n| Stat 1 number | 2cm | 5cm | 9cm | 4cm | serif bold 60–64pt |\n| Stat 1 label | 2cm | 9.5cm | 9cm | 2cm | sans ≥ 16pt (H4 floor) |\n| Stat 2 number / label | 12.5cm | (same) | 9cm | (same) | (same) |\n| Stat 3 number / label | 23cm | (same) | 9cm | (same) | (same) |\n\n**Use this when** you have 2–3 anchoring numbers and the story is \"three facts argue the point\" — Problem, Why-Now, Market-callout, single-row Traction. Labels ≥ 16pt is the H4 floor (sub-label exception); a number without a label reads as bravado, so never drop labels to 12–14pt to fit more text.\n\n### C.3 4-Stat callout row\n\nSame geometry as C.2 but 4 columns. Numbers 60pt, width 7cm each.\n\n```\n+-------------------------------------+\n|  Title                              |\n|                                     |\n|  73%   12hr   $9M   4.2x            |\n|  lbl   lbl    lbl   lbl             |\n+-------------------------------------+\n```\n\n| Element | X positions | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Title | 1.5cm | 1cm | 30.87cm | 3cm | serif bold 36pt |\n| Stat numbers | 1.5 / 9.5 / 17.5 / 25.5cm | 5cm | 7cm | 4cm | serif bold 60pt |\n| Stat labels | (same X) | 9.5cm | 7cm | 2cm | sans ≥ 16pt |\n\n**Use this when** exactly 4 parallel metrics tell the story and 3 feels under-counted. Prefer C.2 if in doubt — 4 always feels tighter than 3, and wrap risk is real.\n\n> **Wrap warning.** At 60pt in 7cm width, dollar patterns with both `$` and `.` fail: `$9.4M` is 5 glyphs but the wide `$` and `.` in a serif bold make it wrap to 2 lines and destroy the callout. Safe dollar shapes at 60pt/7cm: `$9M`, `$96B`, `$4K` (3–4 chars). Non-dollar shapes: `340%`, `4.2x`, `12.3` safe up to 5 chars. Values ≥ 6 chars (`197min`, `3 Days`) will wrap — either (a) drop font to 44–48pt, (b) abbreviate (`197m`, `$9M`), or (c) shift to C.2 (9cm per stat). Single tokens only, no internal spaces.\n\n### C.4 Chart + Context (chart left, stats right)\n\nChart takes left 55%, 2–3 stacked callouts on the right. The default for Traction / Financials / Market-sizing-with-context.\n\n```\n+-------------------------------------+\n|  Title                              |\n|                                     |\n|  +---------------+   +--------+     |\n|  |               |   | Stat 1 |     |\n|  |    chart      |   +--------+     |\n|  |               |   | Stat 2 |     |\n|  +---------------+   +--------+     |\n+-------------------------------------+\n```\n\n| Element | X | Y | Width | Height |\n|---|---|---|---|---|\n| Title | 2cm | 1cm | 29.87cm | 3cm |\n| Chart | 2cm | 4cm | 17cm | 13cm |\n| Stats column | 21cm | 4cm+ | 11cm | 2.5cm number + 1.5cm label (~3.7cm per pair) |\n\nSub-labels ≥ 16pt (H4 floor). For 5 stats stacked, drop number size to 44pt; 6+ stats means pick a different pattern. Post-batch for column/bar charts: `officecli set \"$FILE\" \"/slide[N]/chart[1]\" --prop gap=80` to tighten bar spacing.\n\n**Use this when** one primary chart drives the story and 2–3 numeric anchors reinforce it — Traction (ARR curve + current ARR + YoY + NRR), Financials (4-year column chart + assumption callouts), Market (bar chart + SOM / CAGR / methodology).\n\n### C.5 Icon-in-circle grid (3-row vertical)\n\n3 vertical rows, each = circle icon on the left + title + 1-line description.\n\n```\n+---------------------------------------+\n|  Title                                |\n|                                       |\n|  (o)  Label one                       |\n|       description one                 |\n|                                       |\n|  (o)  Label two                       |\n|       description two                 |\n|                                       |\n|  (o)  Label three                     |\n|       description three               |\n+---------------------------------------+\n```\n\n| Element | X | Y positions | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Icon circle | 2cm | 4.5 / 8.5 / 12.5cm | 2.5cm | 2.5cm | ellipse, accent fill |\n| Label | 5.5cm | (icon Y + 0) | 25cm | 1.2cm | sans bold 18pt |\n| Description | 5.5cm | (icon Y + 1.3cm) | 25cm | 1.8cm | sans ≥ 16pt (H4 floor), muted |\n\n**Use this when** you have 3 short vertical points that benefit from a visual anchor per row — Solution mechanism, Value pillars, Product loop. Choose C.5b (2×2 grid) when items are parallel and you have exactly 4; choose a horizontal 5-across variant when icons should read side-by-side (e.g. 5-step process).\n\n### C.5b 2×2 Feature grid (4 parallel items)\n\n4 rounded cards, 2 columns × 2 rows. Use when you have exactly 4 parallel items (product pillars, service types, feature quadrants).\n\n```\n+-----------------------------+\n|  Title                      |\n|                             |\n|  +---------+  +---------+   |\n|  | (o) T1  |  | (o) T2  |   |\n|  | body    |  | body    |   |\n|  +---------+  +---------+   |\n|  +---------+  +---------+   |\n|  | (o) T3  |  | (o) T4  |   |\n|  | body    |  | body    |   |\n|  +---------+  +---------+   |\n+-----------------------------+\n```\n\n| Element | X | Y | Width | Height | Font / size |\n|---|---|---|---|---|---|\n| Slide title | 2cm | 1cm | 29.87cm | 2.5cm | serif bold 32pt |\n| Card 1 bg (top-left) | 1.5cm | 4cm | 14.5cm | 7cm | roundRect |\n| Card 2 bg (top-right) | 17.5cm | 4cm | 14.5cm | 7cm | roundRect |\n| Card 3 bg (bottom-left) | 1.5cm | 12cm | 14.5cm | 7cm | roundRect |\n| Card 4 bg (bottom-right) | 17.5cm | 12cm | 14.5cm | 7cm | roundRect |\n| Icon ellipse (each card) | card_x + 0.5cm | card_y + 0.5cm | 2cm | 2cm | — |\n| Card title (each) | card_x + 3.2cm | card_y + 0.6cm | 10.5cm | 1.8cm | sans bold 16pt |\n| Card body (each) | card_x + 0.5cm | card_y + 3cm | 13cm | 3.5cm | sans ≥ 16pt (H4 floor) |\n\n**Use this when** you have exactly 4 parallel items and the eye should land on each equally — 4 product pillars, 4 service tiers, 4 stakeholder types. 3 items feel lonely in a 2×2; 5+ items break the grid — go to a 3×2 (see pptx v2 §(d) grid math) or C.5 row pattern.\n\n> **Z-order canon (critical).** Each card's `roundRect` background must be added immediately before that card's icon / title / body shapes in the batch JSON — pptx paints in insertion order, so a background added after its text paints over and hides the text. When building with `officecli batch`, follow the per-card sequence `bg → ellipse → title → body` strictly. Pattern and z-order details → see pptx v2 §Recipe (c) z-order canon; reuse grid math from pptx v2 §(d) for non-2×2 counts.\n\n**Dark-background variant.** Change card fill from `F0F4F8` (light) to a lighter-dark shade like `1A2540` and bump body text to `FFFFFF` / `E8E8E8`. Palette variables (e.g. `$MUTED`) do NOT expand inside single-quoted heredocs — write the literal hex (`64748B`) in the JSON.\n\n---\n\n## Key-slide recipes (10 essentials)\n\nThe 10 slides every pitch deck carries. Each recipe below gives: **visual outcome** (what the slide looks like from 3m away) + **runnable block** (≤ 18 lines) + **QA one-liner**. All recipes inherit pptx v2 palettes, grid math, type hierarchy, and `--prop tailEnd=triangle` on every connector. Recipes reference the Slide Patterns above: Cover reuses C.1; Problem / Why-Now reuse C.2; Traction / Financials reuse C.4; Feature / pillar slides reuse C.5b. `$FILE` is your deck file.\n\n**Long-title wrap rule.** A 36pt+ title that wraps to 2 lines: add `height` (e.g. 2cm → 3.5cm) — never drop the font below 36pt. Titles < 36pt on a pitch deck read as timid regardless of content.\n\n> **Chart `series1.color=` on `add` works** (applies to every chart recipe below). Passing `--prop series1.color=` (or `series2.color=`, …) on chart `add` applies the series color and exits 0. Verify with a readback if you like: `officecli get \"$FILE\" \"/slide[N]/chart[1]/series[1]\" --json | jq '.data.results[0].format.color'`.\n\n### (1) Cover slide — company · tagline · round · date\n\n**Visual outcome.** Dark navy fill, centered 44pt company name, 20pt one-line tagline underneath, small 16pt meta line at the bottom with round + amount + date. Thin brand band at the very bottom (0.5cm high) in the accent color.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=BrandBand \\\n  --prop geometry=rect --prop fill=CADCFC \\\n  --prop x=0cm --prop y=18.5cm --prop width=33.87cm --prop height=0.55cm\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=CoverTitle --prop text=\"Acme DevOps\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=Tagline --prop text=\"Kubernetes observability, built for production at scale\" \\\n  --prop x=2cm --prop y=10.5cm --prop width=29.87cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=20 --prop color=CADCFC --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[1]\" --type shape --prop name=CoverMeta --prop text='Series B · $35M · April 2026' \\\n  --prop x=2cm --prop y=15cm --prop width=29.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=16 --prop color=FFFFFF --prop align=center --prop fill=none\n```\n\n**QA.** Cover has 4 discrete elements (brand band + title + tagline + meta). 80%-whitespace covers fail the pptx \"cover ≥ 60% filled\" floor.\n\n**Consumer variant (3-second grab).** Consumer decks (B2C app / hardware / D2C) should add a single dominant motif — hero product shot, oversized company name (60–96pt), or symbolic mark (crescent moon / abstract geometric). Replace the 44pt title with an 80–96pt name + one motif shape (`--type shape --prop geometry=ellipse --prop fill=<accent>` for an abstract mark, or `picture` at ~40% of slide for a product hero). Keep tagline + round + date identical. SaaS / B2B may skip — the typographic-only cover is sufficient.\n\n### (2) Problem slide — industry pain in 1 sentence + 3 data cards\n\n**Visual outcome.** 36pt title stating the pain (not \"The Problem\"). Below, three equal-width data cards across the slide: each a giant number (40pt) + one-line qualifier (16pt) + source footnote (12pt gray).\n\nGrid math for 3 cards, 1.5cm margins, 0.76cm gap: `usable = 33.87 − 3 − 2·0.76 = 29.35`, `col_width = 29.35 / 3 = 9.78cm`. x-positions: `1.5 / 12.04 / 22.58`.\n\n```bash\nSLIDE=2  # second slide, after cover. Adjust from your build order.\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Kubernetes debugging burns 12 engineering hours / incident\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.5cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"PC1\",\"geometry\":\"roundRect\",\"fill\":\"F5F7FA\",\"x\":\"1.5cm\",\"y\":\"5cm\",\"width\":\"9.78cm\",\"height\":\"10cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"73%\",\"x\":\"1.5cm\",\"y\":\"6cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"of incidents take > 1 hour to diagnose\",\"x\":\"1.5cm\",\"y\":\"9.5cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Calibri\",\"size\":\"18\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Source: 2025 DORA Report\",\"x\":\"1.5cm\",\"y\":\"13cm\",\"width\":\"9.78cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"12\",\"italic\":\"true\",\"color\":\"666666\",\"align\":\"center\",\"fill\":\"none\"}}\n]\nEOF\n# Repeat the 4-block pattern at x=12.04cm and x=22.58cm for cards 2 and 3.\n```\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"Source\")'` returns ≥ 3 (every claim carries a source). If zero sources, VCs will not trust a single number.\n\n### (2b) Why Now slide — Consumer / Seed / early A must-have\n\n**Visual outcome.** 3 cards across: each = **trigger headline** (24pt bold) + **data point** (60pt number or date) + **one-line implication** (16pt) + **source footnote** (12pt gray). Reuse Problem grid math (`col=9.78cm`, x = `1.5 / 12.04 / 22.58`). §赛道 Consumer row 2 must-have; Seed / early A in any vertical benefits when \"market window\" IS the thesis.\n\n```bash\nSLIDE=3\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Why now: three converging triggers\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.5cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# Card 1 (x=1.5cm) — trigger / data / implication / source. Repeat at x=12.04cm and x=22.58cm.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"geometry\":\"roundRect\",\"fill\":\"F5F7FA\",\"x\":\"1.5cm\",\"y\":\"5cm\",\"width\":\"9.78cm\",\"height\":\"10cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"BOM cost\",\"x\":\"1.5cm\",\"y\":\"5.5cm\",\"width\":\"9.78cm\",\"height\":\"1.2cm\",\"font\":\"Calibri\",\"size\":\"24\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"−90%\",\"x\":\"1.5cm\",\"y\":\"7cm\",\"width\":\"9.78cm\",\"height\":\"3cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"B85042\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Wearable BOM fell 90% since 2021; sub-$40 retail now viable\",\"x\":\"1.5cm\",\"y\":\"11cm\",\"width\":\"9.78cm\",\"height\":\"2cm\",\"font\":\"Calibri\",\"size\":\"16\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Source: IDC Wearables Teardown 2025\",\"x\":\"1.5cm\",\"y\":\"13.5cm\",\"width\":\"9.78cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"12\",\"italic\":\"true\",\"color\":\"666666\",\"align\":\"center\",\"fill\":\"none\"}}\n]\nEOF\n# Card 2 pattern: Oura IPO 2024 / +$2.4B valuation / category proven. Card 3: On-device LLM (Llama 3.2) / Q4-24 / privacy moat viable.\n```\n\n**QA.** 3 cards, each with a date/year citation in the source footnote, each card ≤ 30 words. `officecli query \"$FILE\" 'shape:contains(\"2024\")'` + `'shape:contains(\"2025\")'` ≥ 2 combined (timing anchors visible).\n\n### (3) Solution slide — product in one sentence + 3-step \"how it works\"\n\n**Visual outcome.** 36pt title naming the product pattern (not \"Our Solution\"). Below: 3 or 4 rounded boxes horizontally at y=7cm with elbow connectors + triangle arrowheads. Each box = one verb (observe / correlate / resolve). Reuse pptx Recipe (c) flowchart — orchestration, not a new primitive.\n\n```bash\n# Title — \"a product pattern, not a brand slogan\".\n# Good: \"Auto-correlate K8s events across 3 data planes in 90 seconds\"\n# Bad:  \"The future of observability\"\nSLIDE=4\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop name=SolTitle \\\n  --prop text=\"Correlate K8s events across 3 data planes in 90 seconds\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2.2cm \\\n  --prop font=Georgia --prop size=32 --prop bold=true --prop color=1E2761 --prop fill=none\n# 3 boxes across: gap = (33.87 − 3 − 3·7) / 2 = 4.93cm; x = 1.5, 13.43, 25.36\n# Connectors + arrowheads: --prop tailEnd=triangle ALWAYS (pptx Known Issues C-P-5..6).\n# Full batch block → see pptx v2 §Creating and Editing (c) 4-step flowchart; swap N from 4 boxes to 3.\n```\n\n**Product-pattern title rule.** The solution title is a verb + differentiated mechanism + metric. \"Observe / Correlate / Resolve\" is generic; VCs read it as any APM vendor. \"Correlate K8s events across 3 data planes in 90 seconds\" is specific; VCs read it as an insight.\n\n**QA.** Count connectors: `officecli query \"$FILE\" 'connector' --json | jq '.data.results | length'` ≥ (step_count − 1). Every connector must have `tailEnd=triangle` — `view annotated` confirms arrowhead direction. Title must be ≤ 12 words (one breath).\n\n### (4) Market slide — TAM / SAM / SOM nested columns\n\n**Visual outcome.** 36pt title \"Market: $X.YB growing Z% CAGR\". Below: three horizontal bars (or three stacked nested rectangles), labeled TAM / SAM / SOM with dollar values + growth rate. Bottom footnote cites **top-down vs bottom-up source** — pick one methodology per deck, don't mix.\n\n```bash\n# Use a pptx column chart with 3 values. Categories = TAM,SAM,SOM. Source annotation is a separate shape.\nSLIDE=5\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"$42B observability market, 18% CAGR\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=bar \\\n  --prop series1.name=\"USD (billions)\" --prop series1.values=\"42,8.4,0.62\" --prop series1.color=1E2761 \\\n  --prop categories=\"TAM,SAM,SOM (5-yr)\" \\\n  --prop x=2cm --prop y=4cm --prop width=22cm --prop height=12cm \\\n  --prop title='Market sizing — bottom-up by enterprise count × ACV'\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='Source: Gartner 2025 APM Magic Quadrant; SAM = 20% of TAM (K8s-first shops); SOM = 7.4% of SAM over 5 years at 18-24% share.' \\\n  --prop x=2cm --prop y=16.5cm --prop width=29.87cm --prop height=2cm \\\n  --prop font=Calibri --prop size=12 --prop italic=true --prop color=666666 --prop fill=none\n```\n\n**QA.** Top-down vs bottom-up MUST be declared in the source footnote. A TAM without methodology reads as fabricated.\n\n### (5) Product slide — screenshot + 3 bullets OR 3-card feature grid\n\n**Visual outcome.** Two layout options: (a) hero product screenshot on the left (60% of slide), 3 one-line feature bullets on the right (each ≥ 18pt body, no bullets under bullets). (b) 3 feature cards with one icon / screenshot thumbnail each. Pick (a) for consumer / app products, (b) for B2B / infrastructure.\n\n```bash\n# (a) screenshot + bullets — consumer pattern\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type picture --prop src=product_hero.png \\\n  --prop x=1cm --prop y=4cm --prop width=18cm --prop height=13cm\nofficecli set \"$FILE\" \"/slide[$SLIDE]/picture[1]\" --prop alt=\"Product UI: dashboard with 12 K8s clusters, live correlation graph\"\n# Right column bullets (each as a separate shape so sizes stay explicit)\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Auto-correlate across 3 data planes\" \\\n  --prop x=20cm --prop y=5cm --prop width=12cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=20 --prop bold=true --prop color=1E2761 --prop fill=none\n# Repeat for bullets 2 and 3 at y=7.5cm / y=10cm.\n```\n\n**QA.** Picture alt text present (`query 'picture:no-alt'` = empty). Bullets each ≥ 18pt. No \"Lorem\"/\"product name here\"/`{{...}}` tokens.\n\n### (6) Business model slide — unit econ or revenue model\n\n**Visual outcome.** Decision tree by vertical:\n- **SaaS / Enterprise (Series A+)** — 4 KPI callouts: CAC / LTV / Payback / GM (reuse pptx Recipe (e)).\n- **Consumer / D2C** — AOV · repeat-purchase rate · contribution margin · blended CAC.\n- **Marketplace** — GMV / take-rate / liquidity metric / cohort retention.\n- **Bio / Deep tech** — revenue model (license / milestone / royalty split) with assumed ranges.\n\nTitle names the dominant metric (e.g. \"LTV:CAC 4.7x · 14-month payback · 78% gross margin\"), not \"Business Model\". Full 4-card batch block → see pptx v2 §(e) KPI callouts.\n\n```bash\n# SaaS pattern: KPI card values + sub-label + gray VC-floor context under each.\n# Card 1 (LTV): big number \"$420K\", sub \"Lifetime value\", context \"floor: ARPU × GM / churn\"\n# Card 2 (CAC): big number \"$90K\",  sub \"Acquisition cost\", context \"fully-loaded S&M spend\"\n# Card 3 (Payback): big number \"14 mo\", sub \"CAC payback\", context \"VC floor: < 18 mo\"\n# Card 4 (GM): big number \"78%\", sub \"Gross margin\", context \"SaaS floor: 70%+\"\n# Grid math for 4 cards across: usable = 33.87 − 3 − 3·0.76 = 28.59, col = 7.15cm\n# → Full batch template → pptx v2 §(e). Adapt card count 3→4 and card width 9.78cm→7.15cm.\n```\n\n**QA.** For Series B+, all four of {CAC, LTV, payback, GM} present: `officecli query \"$FILE\" 'shape:contains(\"CAC\")'` ≥ 1 AND `shape:contains(\"LTV\")'` ≥ 1 AND `shape:contains(\"payback\")'` ≥ 1 AND `shape:contains(\"gross margin\")'` ≥ 1.\n\n### (7) Traction slide — ARR curve that starts at 0\n\n**Visual outcome.** Line chart taking 60% of slide width; ARR on y-axis **starting at 0** (not at 80% of current value — the VC hockey-stick lie). Right-side commentary card: single giant number (current ARR) + growth rate + 2-3 milestones. If Series B+, second row: cohort retention snippet or logo wall.\n\n```bash\nSLIDE=7\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='ARR: $0 → $18M in 24 months' \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=line \\\n  --prop series1.name=ARR --prop series1.values=\"0.2,0.6,1.4,3.2,6.1,11.3,15.8,18.0\" --prop series1.color=1E2761 \\\n  --prop categories=\"Q1-24,Q2-24,Q3-24,Q4-24,Q1-25,Q2-25,Q3-25,Q4-25\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=21cm --prop height=13cm \\\n  --prop title='Quarterly ARR ($M) — y-axis anchored at 0' \\\n  --prop axismin=0\n# Right callout\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop geometry=roundRect --prop fill=1E2761 --prop line=none \\\n  --prop x=23.5cm --prop y=4cm --prop width=8.8cm --prop height=13cm\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$18M' \\\n  --prop x=23.5cm --prop y=5cm --prop width=8.8cm --prop height=3cm \\\n  --prop font=Georgia --prop size=64 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"ARR · +312% YoY · NRR 128%\" \\\n  --prop x=23.5cm --prop y=9cm --prop width=8.8cm --prop height=3cm \\\n  --prop font=Calibri --prop size=18 --prop color=CADCFC --prop align=center --prop fill=none\n```\n\n**`--prop axismin=0` is load-bearing** — without it, pptx auto-scales the y-axis to start near the lowest value. That is the hockey-stick lie. Gate 6 greps this below.\n\n**QA.** ARR curve chart must carry `axismin=0`. `officecli get \"$FILE\" \"/slide[$SLIDE]/chart[1]\" --json | jq .format.axisMin` returns `0` (CLI emits camelCase `axisMin` in readback even though input prop is lowercase `axismin`).\n\n### (8) Team slide — avatars + names + prior companies (not just a wall)\n\n**Visual outcome.** 3- or 4-card row across the middle of the slide. Each card: picture (6×6cm) on top; name (20pt bold); role (16pt); **prior company + title** (16pt italic, 1 key line); optional LinkedIn URL footer (12pt). Team slide with just headshots and names reads as amateur.\n\n```bash\nSLIDE=11\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Team: 3 prior exits, 42 years combined K8s\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# Card 1 — CEO\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type picture --prop src=alice.jpg \\\n  --prop x=2cm --prop y=5cm --prop width=6cm --prop height=6cm\nofficecli set \"$FILE\" \"/slide[$SLIDE]/picture[1]\" --prop alt=\"Alice Chen, CEO — portrait\"\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Alice Chen\" \\\n  --prop x=2cm --prop y=11.5cm --prop width=6cm --prop height=1cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761 --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"CEO\" \\\n  --prop x=2cm --prop y=12.8cm --prop width=6cm --prop height=0.8cm \\\n  --prop font=Calibri --prop size=16 --prop color=333333 --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"ex-Datadog Director (Series C → IPO); led K8s observability GTM $40M → $200M ARR\" \\\n  --prop x=2cm --prop y=13.8cm --prop width=6cm --prop height=2.5cm \\\n  --prop font=Calibri --prop size=14 --prop italic=true --prop color=333333 --prop align=center --prop fill=none\n# Repeat for Card 2 (CTO, x=10cm) and Card 3 (VP Eng, x=18cm) — 3 cards × 5-6 shapes each.\n```\n\nPrior companies carry **credibility density**. VCs read \"ex-Datadog Director + led $40M → $200M\" in 2 seconds; they read \"co-founder, passionate\" in 0 seconds (because they skip it). Advisors, if shown, go in a smaller row below with a single logo each.\n\n**Arrangement helper.** 3 cards: `col=9.78cm, x=1.5/12.04/22.58`. 4 cards: `col=7.15cm, x=1.5/9.41/17.32/25.23`. 5 cards: `col=5.85cm, x=1.5/7.75/14.0/20.25/26.5` (0.4cm gap, tighter). 6+ or asymmetric → 2-row grid (3×2 / 3×3); see pptx v2 §(d) grid math.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"ex-\")'` + `'shape:contains(\"prior\")'` + `'shape:contains(\"former\")'` ≥ 1 per team member. If zero, you have a portfolio, not a team.\n\n### (9) Financials slide — 4-year plan + honest assumptions\n\n**Visual outcome.** Column chart: 4 years × (revenue, gross margin $, EBITDA). Right-side card: 3-bullet assumption panel (ARPU assumption, win-rate assumption, churn assumption). Title names the trajectory (\"$18M → $85M by FY29\"), not \"Financial Projections\".\n\nReuse pptx Recipe (b) chart + commentary. Pitch-specific: ASSUMPTIONS column on the right is **load-bearing** — a 4-year plan without visible assumptions reads as aspirational. VCs will ask what's behind every number anyway; surface it.\n\nLeft 2/3 — slide + title + 3-series column chart:\n\n```bash\nSLIDE=17\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$18M → $85M ARR by FY29' \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=column \\\n  --prop series1.name=\"Revenue ($M)\"  --prop series1.values=\"18,34,58,85\" --prop series1.color=1E2761 \\\n  --prop series2.name=\"Gross Margin ($M)\" --prop series2.values=\"14,26,45,68\" --prop series2.color=CADCFC \\\n  --prop series3.name=\"EBITDA ($M)\"   --prop series3.values=\"-6,-2,8,22\" --prop series3.color=B85042 \\\n  --prop categories=\"FY26,FY27,FY28,FY29\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=20cm --prop height=13cm \\\n  --prop title='4-year plan — revenue, GM, EBITDA ($M)'\n```\n\nRight 1/3 — assumptions commentary card:\n\n```bash\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop geometry=roundRect --prop fill=F5F7FA --prop line=none \\\n  --prop x=22.5cm --prop y=4cm --prop width=9.8cm --prop height=13cm\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Key Assumptions\" \\\n  --prop x=23cm --prop y=4.5cm --prop width=8.8cm --prop height=1.2cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761 --prop fill=none\n# 5 assumption bullets as 5 separate paragraph shapes at y=6, 7.5, 9, 10.5, 12cm — size=14, italic=true.\n# Keep each bullet ≤ 14 words so 8.8cm width fits without wrap.\n```\n\n**Assumptions panel is load-bearing.** A 4-year plan without visible assumptions reads as aspirational. VCs ask what's behind every number anyway — surface the three or four assumptions that drive the curve.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"assumption\")'` OR `'shape:contains(\"Assumes\")'` ≥ 1. If zero, add the panel.\n\n### (10) The Ask — hero number + 4-bucket Use-of-Funds + runway\n\n**Visual outcome.** Dark fill (match cover). Hero number in the center top: `$35M` at 96pt white. Below, a 4-bucket pie OR a 4-card row listing **Engineering 40% / GTM 35% / G&A 15% / Reserve 10%**. Bottom line: \"18-month runway to $40M ARR\" (next milestone, not \"until next round\").\n\n```bash\nSLIDE=20\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='$35M Series B' \\\n  --prop x=2cm --prop y=2cm --prop width=29.87cm --prop height=4cm \\\n  --prop font=Georgia --prop size=88 --prop bold=true --prop color=FFFFFF --prop align=center --prop fill=none\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type chart --prop chartType=pie \\\n  --prop series1.name=\"Use of Funds\" --prop series1.values=\"40,35,15,10\" \\\n  --prop categories=\"Engineering,Go-to-Market,G&A,Reserve\" \\\n  --prop colors=\"CADCFC,B85042,97BC62,FFFFFF\" \\\n  --prop x=6cm --prop y=7cm --prop width=12cm --prop height=10cm \\\n  --prop title=\"Use of Funds — 4 buckets\"\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='18 months runway to $40M ARR and Series C' \\\n  --prop x=2cm --prop y=17cm --prop width=29.87cm --prop height=1.5cm \\\n  --prop font=Calibri --prop size=22 --prop color=CADCFC --prop align=center --prop fill=none\n```\n\n**4-bucket convention.** Engineering / GTM / G&A / Reserve is the canonical breakdown. Typical Series A ranges: Eng 40-50%, GTM 30-40%, G&A 10-15%, Reserve 5-10%. Series B shifts 5-10 points from Eng to GTM.\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"Use of Funds\")'` ≥ 1. Pie chart present on ask slide. Runway + milestone on ask slide.\n\n### (11) Pipeline chart — Bio / Deep Tech must-have\n\n**Visual outcome.** Horizontal swimlane. Left column = candidate name; 4 stage columns to the right (Preclinical / Ph1 / Ph2 / Ph3 for bio — or TRL1-3 / TRL4-6 / TRL7-8 / TRL9 for deep tech). Each row's bar extends to its current stage; darker fill for later stages. NCT / trial-ID footer below. §赛道 row 5 Bio must-have; SaaS / Consumer skip.\n\nGrid math: usable `= 30.87cm`, candidate col `= 7cm`, stage cols `= (30.87 − 7) / 4 = 5.97cm` each, row height `= 2.3cm`. Stage col x: `8.5 / 14.47 / 20.44 / 26.41`.\n\n```bash\nSLIDE=6\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text=\"Pipeline: 3 candidates across Ph1–Ph3\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30.87cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n# 4 stage headers + candidate row 1 (HLX-201 at Ph2, bar width = 3·5.97 = 17.91cm) in one batch.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Preclinical\",\"x\":\"8.5cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 1\",\"x\":\"14.47cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 2\",\"x\":\"20.44cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"Phase 3\",\"x\":\"26.41cm\",\"y\":\"4cm\",\"width\":\"5.97cm\",\"height\":\"1cm\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"333333\",\"align\":\"center\",\"fill\":\"F5F7FA\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"HLX-201 (lead)\",\"x\":\"1.5cm\",\"y\":\"5.5cm\",\"width\":\"7cm\",\"height\":\"1.5cm\",\"font\":\"Calibri\",\"size\":\"18\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"left\",\"fill\":\"none\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"geometry\":\"roundRect\",\"fill\":\"1E2761\",\"x\":\"8.5cm\",\"y\":\"5.7cm\",\"width\":\"17.91cm\",\"height\":\"1.1cm\",\"line\":\"none\"}}\n]\nEOF\n# Repeat rows 2 & 3 at y=7.8cm / y=10.1cm with bar widths per stage (Ph1=5.97cm, Ph1-Ph2=11.94cm, Ph1-Ph3=17.91cm).\n# NCT footer full-width at y=16.8cm.\nofficecli add \"$FILE\" \"/slide[$SLIDE]\" --type shape --prop text='NCT05021323 (HLX-201, Ph2, n=48) · NCT06142091 (HLX-304, Ph1, n=24) · IND-filed Q1-26 for HLX-412' \\\n  --prop x=1.5cm --prop y=16.8cm --prop width=30.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=12 --prop italic=true --prop color=666666 --prop fill=none\n```\n\n**QA.** `officecli query \"$FILE\" 'shape:contains(\"NCT\")' --json | jq '.data.results | length'` ≥ 1. Bar colors darken across stages (`CADCFC` preclinical-only, `1E2761` Ph2-reached).\n\n### (12) Competitive comparison table — Series B+ essential\n\n**Visual outcome.** 5–7 rows × 4–6 cols. Column 1 = competitor name (optional logo shape beside); rest = differentiators (speed / price / integrations / margin / coverage). **Last row = your company, fill highlighted** in an accent color (CADCFC / 97BC62); competitor rows gray. Every Series B+ deck needs this (SaaS: Datadog / New Relic / Splunk; Bio: Kite / Novartis / BMS).\n\n```bash\nSLIDE=13\nofficecli add \"$FILE\" / --type slide --prop layout=blank --pro","frontmatter":{"name":"officecli-pitch-deck","description":"Use this skill when the user is building a fundraising / investor pitch deck — seed, Series A / B / C, convertible note, SAFE round, strategic raise. Trigger on: 'pitch deck', 'investor deck', 'Series A deck', 'Series B deck', 'Series C deck', 'fundraising deck', 'seed pitch', 'VC deck', 'raising capital', 'term sheet presentation'. Output is a single .pptx. This skill is a scene layer on top of officecli-pptx — inherits every pptx v2 rule (visual floor, grid, palettes, connector canon, Delivery Gate). DO NOT invoke for a generic board review, sales deck, all-hands, or product launch — route those to officecli-pptx base."},"isInternal":false,"tokens":14925,"sizeBytes":50519},{"name":"SKILL.md","path":"skills/officecli-pptx/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-pptx/SKILL.md","title":"officecli-pptx","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-pptx\ndescription: \"Use this skill any time a .pptx file is involved -- as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file; editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions 'deck', 'slides', 'presentation', 'pitch', or references a .pptx filename.\"\n---\n\n# OfficeCLI PPTX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good slides look like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help pptx                         # List all pptx elements\nofficecli help pptx <element>               # Full element schema (e.g. shape, chart, animation, connector, zoom, group)\nofficecli help pptx <verb> <element>        # Verb-scoped (e.g. add shape, set slide)\nofficecli help pptx <element> --json        # Machine-readable schema\n```\n\nHelp reflects the installed CLI version. When skill and help disagree, **help is authoritative**. Triggers to run help immediately: `UNSUPPORTED props:` warning, unknown animation preset, `connector.shape=` enum drifts, prop-vs-alias (`lineWidth` vs `line.width`, `color` vs `font.color`).\n\n## Shell & Execution Discipline\n\n**Shell quoting (zsh / bash).** ALWAYS quote element paths (`\"/slide[1]/...\"`) — zsh globs unquoted `[1]` to `no matches found`. Escaping happens at three layers — keep them separate (the CLI handles the second for you):\n\n1. **Shell.** `$` in a value still belongs to the shell — single-quote the whole value: `--prop text='$15M'`. Double-quoted `\"$15M\"` gets expanded to `M`. The CLI does NOT unescape `\\$` for you.\n2. **CLI (`text=`).** The two-char escapes `\\n` and `\\t` ARE interpreted, consistently across pptx / docx / xlsx — `\\n` is a line / paragraph break, `\\t` is a tab. To produce a literal backslash-n in text, double it (`\\\\n`); this is rarely what you want.\n3. **JSON (batch heredoc).** The recipes below pipe `cat <<EOF | officecli batch` **unquoted** so `$SLIDE` / `$FILE` expand inside the body. That same unquoted heredoc also expands a literal `$` in a value: `\"$1.42\"` silently becomes `.42` and `\"$2.4B\"` becomes `.4B` (`$1` / `$2` are empty shell params). **Escape currency as `\\$`** — `\"text\":\"\\$1.42\"` — which still lets `$SLIDE` expand. `\\n` / `\\t` inside the JSON work either way. A fully-quoted `<<'EOF'` protects every `$` but then `$SLIDE` won't expand, so only use it when the body has no shell variables. After writing money values, `view text` and confirm the `$` survived.\n\nIf in doubt, `view text` after writing and compare character-for-character.\n\n**Incremental execution.** One command → check exit code → continue. A 50-command script that fails at command 3 cascades silently. After any structural op (new slide, chart, animation, connector) run `get` before stacking more.\n\n## Requirements for Outputs\n\nThese are the deliverable standards every deck MUST meet. Violating any one = not done, regardless of content quality.\n\n### All decks\n\n**One idea per slide.** If a slide needs a second title to explain what it covers, split it. Dense \"everything about X\" slides lose the audience inside 3 seconds. Use a section divider to group related one-idea slides, not a mega-slide.\n\n**Explicit type hierarchy — do NOT rely on theme defaults.** Theme defaults drift between masters. Set sizes explicitly on every text shape.\n\n| Element | Minimum | Typical | Min shape height |\n|---|---|---|---|\n| Slide title | **≥ 36pt** bold | 36–44pt | ≥ 2cm |\n| Section / subtitle | ≥ 20pt | 20–24pt | ≥ 1.2cm |\n| Body text | **≥ 18pt** | 18–22pt | ≥ 1cm |\n| Caption / axis label | ≥ 10pt muted | 10–12pt | ≥ 0.6cm |\n\nRule of thumb: **min shape height ≈ font_pt × 0.05cm**. An 18pt sublabel in a 0.8cm-tall box will overflow — `view annotated` catches this.\n\nTitle must be **≥ 2× body size** (36pt over 20pt works; 28pt over 20pt looks timid). Four legit exceptions to body ≥ 18pt: chart axis labels, legends, footer / page number, and ≤ 5-word KPI sublabels (e.g. \"Active users\"). Descriptive sentences must be ≥ 18pt. Left-align body; center only titles and hero numbers. If \"the cards won't fit\", drop cards instead of shrinking font.\n\n**Two fonts max, one palette.** One heading font + one body font (e.g. Georgia + Calibri) — a third *display* face is fine only for big numerals or the cover title, as long as that heading+body pair stays intact. One dominant brand color (60–70% weight) + one supporting + one accent. Never mix 4+ colors in body content. **The palettes and font pairings in Design Principles are a floor, not a menu:** if the user gave brand colors/fonts or an existing template, match those first; otherwise the named sets are calibrated seeds — blend or diverge freely, as long as the result isn't *worse* than them and still clears the contrast floor.\n\n**Every slide carries a non-text visual — one that informs.** Shape, chart, icon, gradient band that carries meaning, not decoration. A bullet-only deck is interchangeable with a Word doc. Exceptions: literal quote slides, code blocks, a single summary-table slide.\n\n**Less is more — every element earns its place.** The visual rule above guards against bullet-walls; it is not licence to clutter. Don't pad with decorative stats, icons, or filler sections that don't inform (\"data slop\"). If a slide feels empty, fix it with layout and whitespace, not invented content — cut scope rather than bulk it up, and flag a larger addition instead of making it unprompted.\n\n**Speaker notes on every content slide.** `--type notes --prop text=\"...\"`. The speaker needs a script; the audience shouldn't read the slide verbatim.\n\n**Copy reads human, not AI.** Titles orient on content, not punchline. No \"It's not X. It's Y.\", no manufactured tension, no faux-insight (\"The magic moment\"), no one-word drama (\"Momentum.\"). Cut hype adjectives (seamless, robust, game-changing) — let the number carry it.\n\n**Preserve existing templates.** When a file already has a theme and masters, match them. Existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY deck)\n\nBefore declaring done, the per-slide render (see QA) MUST satisfy:\n\n- **No placeholder tokens rendered as content.** `{{name}}`, `$fy$24`, `<TODO>`, `lorem`, `xxxx`, empty `()`/`[]` in chart titles never appear.\n- **No overflow off-edge, no clipped text in shapes.** `view issues` flags both (`shape_off_slide` + a text-fit hint). To fix a clip: grow the box or shorten the value — never trim content to fit.\n- **Cover carries its orienting elements.** Title + subtitle + presenter/client + date + a brand band or key-takeaway strap — a title-only cover reads as a stub. Generous whitespace around them is still right; rich ≠ crowded.\n- **Contrast.** `view issues` auto-flags the common case — opaque dark text on a shape's own dark fill (`low_contrast`). It can't see the rest: icon / chart-series fills, scheme/inherited colors, or text over a *separate* background shape. So on any fill with brightness < 30% (`1E2761`, `36454F`, deep forest / berry / cherry), still confirm every body run, card body, chart series, and icon is `FFFFFF` or brightness > 80% — mid-gray (`6B7B8D` ≈ 44%) reads on a laptop and vanishes on projection. Spot-check via `view html` after the dark-fill pass.\n\nIf any fails, STOP and fix before declaring done.\n\n## Design Principles\n\nA deck is not a document. The audience has 3 seconds to get each slide. Before adding anything, ask: \"If the audience reads only the biggest element and glances once, do they get the point?\" If they have to read the bullets, the biggest element is wrong.\n\n### Grid, margins, negative space\n\nStandard widescreen is **33.87 × 19.05cm**. Treat it as a 12-column grid internally:\n\n- **Edge margin ≥ 1.27cm** (0.5\") on all sides.\n- **Inter-block gap ≥ 0.76cm** (0.3\") between cards / columns / rows — pick one value (0.76 or 1.27cm) and use it everywhere; mixed gaps look unfinished.\n- **≥ 20% negative space per slide.** Filling every pixel reads as amateur.\n- **Compose, don't web-center.** Whitespace is structural: a slide top-weighted with open space in the lower third is correct composition, not an empty defect. Intentional asymmetry (content left, breathing room right) reads more designed than centering everything — don't fill a gap just because it's there.\n- For card grids: `usable = 33.87 − 2·margin − (N−1)·gap`, then `col_width = usable / N`. Don't hand-pick x coordinates.\n\n### Font pairings\n\nPair by document register, not by novelty. \"Best For\" is a prompt, not a decree; a pairing outside this table is fine if it fits — these 8 are seeds, not the set.\n\n| Header | Body | Best For |\n|---|---|---|\n| Georgia | Calibri | Formal business, finance, executive reports |\n| Arial Black | Arial | Bold marketing, product launches |\n| Calibri | Calibri Light | Clean corporate, minimal design |\n| Cambria | Calibri | Traditional professional, legal, academic |\n| Trebuchet MS | Calibri | Friendly tech, startups, SaaS |\n| Impact | Arial | Bold headlines, event decks, keynotes |\n| Palatino | Garamond | Elegant editorial, luxury, nonprofit |\n| Consolas | Calibri | Developer tools, technical / engineering |\n\nSet both fonts explicitly on every shape (`--prop font=Georgia` on titles, `--prop font=Calibri` on body), not via theme inheritance.\n\n### Color and contrast\n\nThe columns: **Primary** (dominant — 60–70% of weight, the color you see first), **Secondary** (supporting tone), **Accent** (sparing, one-hit emphasis), **Text** (body on light fills), **Muted** (captions / axis labels / footer).\n\n| Theme | Primary | Secondary | Accent | Text | Muted |\n|---|---|---|---|---|---|\n| Coral Energy | `F96167` | `F9E795` | `2F3C7E` | `333333` | `8B7E6A` |\n| Midnight Executive | `1E2761` | `CADCFC` | `FFFFFF` | `333333` | `8899BB` |\n| Forest & Moss | `2C5F2D` | `97BC62` | `F5F5F5` | `2D2D2D` | `6B8E6B` |\n| Charcoal Minimal | `36454F` | `F2F2F2` | `212121` | `333333` | `7A8A94` |\n| Warm Terracotta | `B85042` | `E7E8D1` | `A7BEAE` | `3D2B2B` | `8C7B75` |\n| Berry & Cream | `6D2E46` | `A26769` | `ECE2D0` | `3D2233` | `8C6B7A` |\n| Ocean Gradient | `065A82` | `1C7293` | `21295C` | `2B3A4E` | `6B8FAA` |\n| Teal Trust | `028090` | `00A896` | `02C39A` | `2D3B3B` | `5E8C8C` |\n| Sage Calm | `84B59F` | `69A297` | `50808E` | `2D3D35` | `7A9488` |\n| Cherry Bold | `990011` | `FCF6F5` | `2F3C7E` | `333333` | `8B6B6B` |\n\nPick by topic, not by default — finance reads Midnight Executive, a product launch reads Coral Energy, safety / LOTO reads Cherry Bold. If the closest named theme is not quite right, blend (e.g. Forest primary + gold `D4A843` accent). Use **Text** on light fills, **Muted** for captions / axis / footer, `FFFFFF` or Secondary for body on dark fills.\n\nOn dark backgrounds, text and chart series follow the Hard rules contrast floor above.\n\n### Chart-choice decision table\n\nWrong chart type kills the 3-second test:\n\n| Data shape | Use | Avoid |\n|---|---|---|\n| Category comparison (A vs B vs C) | `column` (vertical) / `bar` (≥ 6 categories, horizontal) | pie (slices merge), line (no time axis) |\n| Time series, 1–3 series | `line` | area (occlusion), bar (implies discrete) |\n| Part-of-whole, 2–5 slices | `pie` / `doughnut` | pie with 8+ slices (unreadable) |\n| Correlation / distribution | `scatter` | line (implies ordering) |\n| Multiple categories × metrics, dense | stacked `column` or heatmap | one chart per metric — consolidate |\n| KPI snapshot (single big number) | **Large-text shape** (60–72pt + ≤ 5-word sublabel), NOT a chart | gauge chart, tiny bar |\n\nRule of thumb: if > 3 series and > 8 categories, split into two charts or switch to a table.\n\n### Animation\n\nUse as much or as little as the brand and content call for — a formal finance deck trends to near-zero, a product launch can be more expressive. Animation is a tool, not décor. Three floors keep it from hurting the deck (none caps how much you use):\n\n- **Purposeful** — each one reveals or emphasizes (progressive bullet reveal, a build-up chart), never decorates. If it doesn't aid comprehension, cut it.\n- **Degrades gracefully** — pptx animation renders inconsistently across viewers (Keynote / Slides / web / mobile) and may not play at all, so every slide must read correctly as a *static* frame. Never hide essential content behind a reveal.\n- **Verify live** — animation is runtime-only; `view html` and screenshots can't see it, so confirm in a real presentation viewer before shipping.\n\nTaste steer (not a ban): `fade` / `appear` / a single `zoom-entrance` with snappy durations (~hundreds of ms) fit most decks; `bounce` / `swivel` / `spin` / `fly-from-edge` / dense multi-object choreography usually read amateur — reach for them only when the brand is deliberately playful.\n\n### Layout patterns & data display\n\nVary layout across slides — repeating the same pattern makes every slide feel identical. These are common building blocks, not the full set — pick one per slide, or build a layout outside the table when the content calls for it:\n\n| Pattern | When to use | Key measurement |\n|---|---|---|\n| **Two-column** (text left, visual right) | Concept + evidence; feature + screenshot | Each col ≈ 14-15cm; gap 1cm |\n| **Icon rows** (icon in filled circle + bold header + description) | Feature lists, benefits, team roles | Icon circle 1.5-2cm; 3-4 rows max |\n| **2×2 or 2×3 grid** (card tiles) | Quadrant analysis, SWOT, option comparison | Gap ≥ 0.76cm; consistent card height |\n| **Half-bleed image** (full left or right half, content overlay on other side) | Hero moments, case study openers | Image 16-17cm wide; content column ≥ 14cm |\n| **Large stat callout** (60-72pt number + ≤5-word sublabel below) | Single KPI, milestone, market size | Use shape, NOT a chart; sublabel 14-16pt muted |\n\n**Data display quick rules:**\n- Comparison columns (before/after, A vs B) beat a table for 2-3 options.\n- Timelines and process flows: numbered step shapes + connectors, not a bullet list.\n\n### Image treatment (only when a slide uses a photo / screenshot / logo)\n\n**Read the image first** (open the file) and choose treatment from what you see — don't place blind from a filename.\n\n- **Full-bleed photo** → size to COVER the region (crop edges), no border.\n- **Screenshot / diagram / logo** → size to FIT (never crop content). A transparent or fit image sits on a contrasting fill — drop a colored rectangle behind it, don't let it float on white.\n- **Text over a photo** → never raw on the image. Put it on a card, or lay a protection scrim between image and text (a dark rectangle at ~50–60% opacity, or a gradient fading from the text edge).\n- Never stretch (distort the aspect ratio); don't overlay text on a busy screenshot.\n- Prefer user-provided images / brand assets; no emoji or self-drawn art unless asked.\n\n### Visual motif commitment\n\nPick ONE distinctive element (rounded image frames, section numbers in filled circles, single-side border band, diagonal accent strips) and carry it to every slide — commit across the whole deck; styling one slide and leaving the rest plain reads as abandoned. A secondary motif is fine only if it doesn't compete with the primary. Declare it in your build plan first: `## Motif: numbered circles in brand color`.\n\n### Visual AI-tells to avoid\n\n- **No decorative underline under slide titles.** A stripe / rule below a heading is the single most common AI-slide tell — use whitespace or a background-color change instead.\n- **No rounded-corner card with a colored left-border accent stripe.** The other classic AI-slide tell — use a solid fill, a top accent band, or whitespace separation instead.\n- **No emoji as iconography** unless the brand uses them — use a shape or a real icon asset.\n\nCopy-level tells live in \"Copy reads human\".\n\n## Common Workflow\n\n1. **Open/save lifecycle.** `officecli open <file>` at the start, `officecli save <file>` at the end to flush your edits to disk. `save` only writes — it leaves the resident warm for any follow-up edit; reach for `officecli close <file>` only when you want to release the resident immediately (a one-shot handoff). Both are always safe — they never error or lose work. Use `batch` for repetitive shape grids. **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (python-pptx, PowerPoint, a renderer, delivery).\n2. **Orient.** New deck: `officecli create \"$FILE\"`. Existing: `officecli view \"$FILE\" outline` first. Never edit blind.\n3. **Title sequence first (plan, don't build yet).** Before creating any slide or shape, write out the full ordered list of slide titles. If someone reading ONLY the titles can't follow the argument, fix the arc now — cheaper in a list than after 14 slides. Pick ONE title grammar — all topic noun-phrases or all action statements, never a mix — and hold it throughout (see \"Copy reads human\").\n4. **Build in display order.** Add slides in audience-view order: cover → agenda → section-1 divider → section-1 content → section-2 divider → … → closing. `--index` on slide add works, but linear append keeps the build script readable and avoids index-arithmetic bugs. **Before final delivery, confirm slide count + narrative arc match your build plan.** Gate 3's order-sanity check catches cases where the cover ends up as slide 11 of 14 instead of slide 1.\n5. **Incremental per slide.** Create slide + background, then title, then supporting shapes / charts / connectors. Always `layout=blank` for custom designs. After each structural op, `get /slide[N] --depth 1` to confirm shape IDs.\n6. **Format to spec.** Per the Requirements table; formatting is deliverable, not polish.\n7. **Save + verify.** `officecli save` flushes the file to disk (or `officecli close` to flush and also end the session). Always open in the target presentation viewer before shipping — chart colors, animations, fonts, and zoom are runtime features `view html` can't render. Full verification in QA below.\n8. **QA — assume there are problems.** Fix-and-verify until a cycle finds zero new issues.\n\n## Quick Start\n\nMinimal viable deck: cover + one content slide + notes. `$FILE` stands in for your filename.\n\n```bash\nFILE=\"deck.pptx\"\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\n# Cover — dark fill, centered title\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" /slide[1] --type shape --prop text=\"FY26 Strategic Review\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center\n\n# Content — white fill, title + body + notes\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" /slide[2] --type shape --prop text=\"Revenue grew 18% YoY\" \\\n  --prop x=1.5cm --prop y=1.2cm --prop width=30cm --prop height=2cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761\nofficecli add \"$FILE\" /slide[2] --type shape --prop text=\"Enterprise renewals + new EMEA region drove the beat; NRR held at 118%.\" \\\n  --prop x=1.5cm --prop y=4cm --prop width=30cm --prop height=3cm \\\n  --prop font=Calibri --prop size=20 --prop color=333333\nofficecli add \"$FILE\" /slide[2] --type notes --prop text=\"Lead with the 18% beat, preview EMEA.\"\n\nofficecli save \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nShape of every build: open → slide+background → title → body → notes → save → validate.\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` first, `view text` / `get` / `query` once you know where to look.\n\n```bash\nofficecli view \"$FILE\" outline          # slide count + titles\nofficecli view \"$FILE\" annotated        # complete per-slide breakdown with fonts, sizes, tables, charts\nofficecli view \"$FILE\" text --start 1 --end 5   # text dump (includes table cell text)\nofficecli view \"$FILE\" issues           # empty slides, overflow hints\nofficecli view \"$FILE\" stats            # counts + totals (incl. pictures missing alt)\n```\n\n**Inspect one element.** XPath-style paths, 1-based. ALWAYS quote. Prefer `@name=` / `@id=` selectors over positional `[N]` (stable across reorderings). `[last()]` works. Add `--json` for machine output.\n\n```bash\nofficecli get \"$FILE\" \"/slide[1]\" --depth 1              # shape list with IDs and names\nofficecli get \"$FILE\" \"/slide[1]/shape[@name=Title]\"\nofficecli get \"$FILE\" \"/slide[1]/table[1]\" --depth 3     # table rows / cells\n```\n\n**Query across the deck.** CSS-like selectors; operators `=`, `!=`, `~=`, `>=`, `<=`, `[attr]`, `:contains()`, `:no-alt`. `help pptx query` lists queryable element types.\n\n```bash\nofficecli query \"$FILE\" 'shape:contains(\"Revenue\")'\nofficecli query \"$FILE\" 'picture:no-alt'                 # accessibility gap\nofficecli query \"$FILE\" 'shape[fill=1E2761]'             # color match\nofficecli query \"$FILE\" 'shape[width>=10cm]'             # numeric\n```\n\n**`query --json` output schema.** Results wrap in `.data.results[]` — `jq -r '.data.results[0].format.id'`, NOT `.[0].id`. Shape name is `.name`; fill is `.format.fill`; textColor is `.format.textColor`.\n\n**Visual preview (LEAD).**\n\n```bash\nofficecli view \"$FILE\" html                # prints an HTML preview path; Read it for per-slide visual audit (best structural ground truth)\nofficecli view \"$FILE\" svg --start 3 --end 3   # single slide SVG (charts + gradients do NOT render in SVG)\n```\n\n**Reading the output — an expected non-defect:**\n- **`layout=blank` has no title placeholder.** Titles are plain `shape` elements, so `view outline` reporting `(untitled)` is **expected**, not a defect. Use `layout=title` + `placeholder[title]` only when screen-reader outline compatibility matters.\n\n## Creating & Editing\n\nVerbs: `add` / `set` / `remove` / `move` / `swap` / `batch` / `raw-set`. Ninety percent of a deck is slides, shapes, text, a few charts, pictures, connectors.\n\n### Slides and backgrounds\n\nA slide is `/slide[N]`. Always pass `layout=blank` for custom designs. Background: solid, gradient, or image.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761                 # solid\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop \"background=1E2761-CADCFC-180\"   # gradient (start-end-angle)\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=image:/path/to/hero.jpg  # image background (LEAD)\n```\n\n### Shapes\n\nA `shape` holds text, fill, border, position, and optional animation / link.\n\n```bash\nofficecli add \"$FILE\" /slide[2] --type shape --prop name=Title --prop text=\"Key Insight\" \\\n  --prop x=2cm --prop y=2cm --prop width=20cm --prop height=3cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none\n```\n\nPositioning is explicit — no layout engine, you own the grid math. `--prop preset=` picks geometry (`rect`, `roundRect`, `ellipse`, `triangle`, `arrow`, `star5`, ...); custom `M...Z` paths are not supported — pick a preset. **Name shapes at creation** (`--prop name=HeroTitle`) and address later with `\"/slide[N]/shape[@name=HeroTitle]\"` — names survive z-order / remove-then-add, whereas positional `/shape[3]` (and even `@id=`) shift. Re-`get --depth 1` after any structural change before using positional indexes.\n\n### Text inside shapes (paragraphs, runs, styling)\n\nA shape has paragraphs (`paragraph[K]`) and runs (`run[K]`). For one-line text, `--prop text=` on the shape is enough; a `\\n` in the text makes a paragraph break, `\\t` a tab (see Shell & Execution Discipline; double `\\\\n` for a literal). `add --type paragraph` takes the same style props as a shape (text, align, bold, italic, size, color, font). For mixed styling *within* a line, append a styled run:\n\n```bash\nofficecli add \"$FILE\" \"/slide[2]/shape[@name=Card1]/paragraph[1]\" --type run \\\n  --prop text=\" (inline detail)\" --prop size=14 --prop italic=true --prop color=8899BB\n```\n\n### Charts\n\nPick chart type per the Design Principles chart-choice table. Full prop list (chartType enum, `seriesN.*`, `data=`/`categories=`, axis options): `help pptx add chart`. Typical multi-series with brand colors:\n\n```bash\nofficecli add \"$FILE\" /slide[3] --type chart --prop chartType=column \\\n  --prop series1.name=Revenue --prop series1.values=\"42,45,48\" --prop series1.color=1E2761 \\\n  --prop series2.name=Growth  --prop series2.values=\"2,7,7\"    --prop series2.color=CADCFC \\\n  --prop categories=\"Q1,Q2,Q3\" \\\n  --prop x=2cm --prop y=4cm --prop width=20cm --prop height=10cm\n```\n\nGotchas: (1) chart titles with `()`, `[]`, `TBD` ship as literal text. (2) some viewers normalize chart colors to theme defaults — verify in the target viewer. Series can be added after creation (`add --type series`).\n\n### Pictures\n\n```bash\nofficecli add \"$FILE\" /slide[4] --type picture --prop src=hero.jpg \\\n  --prop x=1cm --prop y=1cm --prop width=32cm --prop height=18cm \\\n  --prop alt=\"Product hero, gradient lit from right\"\n```\n\nConfirm with `officecli query \"$FILE\" 'picture:no-alt'` — must be empty before delivery.\n\n### Connectors (LEAD — flowcharts / decision trees first-class)\n\nDraws a line between two shapes or free coordinates. Full prop / enum reference (`shape`, `headEnd`/`tailEnd` values, `from`/`to` ref forms): `help pptx add connector`.\n\n```bash\nofficecli add \"$FILE\" /slide[5] --type connector \\\n  --prop \"from=/slide[5]/shape[@name=BoxA]\" --prop \"to=/slide[5]/shape[@name=BoxB]\" \\\n  --prop shape=elbow --prop color=333333 --prop tailEnd=triangle\n```\n\n**Every flow connector needs an arrowhead.** Without one, `bentConnector3` renders as a directionless line. `preset=rightArrow` overlay only works for horizontal flows; diamonds / decision trees with diverging edges need `tailEnd=`.\n\n### Animations (LEAD)\n\nUse per the Animation floors above (purposeful, degrades gracefully, verify live). Preset names + duration syntax: `help pptx animation`.\n\n```bash\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=HeroCard]\" --prop animation=fade-entrance-400\nofficecli set \"$FILE\" \"/slide[2]/shape[@name=HeroCard]\" --prop animation=none    # clear all\n```\n\n### Hyperlinks, tooltips, slide-jump\n\n`--prop link=slide[N]` for an in-deck jump (1-based; target slide must exist), `link=nextslide` / `firstslide` / `lastslide` / `previousslide` / `endshow` for named navigation, `link=https://...` for a URL, `--prop tooltip=\"...\"` for hover text.\n\n### Tables, placeholders, groups, zoom — one-liners\n\n- **Tables** — `--type table --prop rows=N --prop cols=M`. Row-level `set` supports `height` and `c1/c2/c3` (seed cell text). Header-row styling is table-level (`firstRow=true` / `headerFill=`), not a row prop. Cell formatting lives on the cell paragraph / run. Populate rows BEFORE setting table-level font (font cascade gets reset by row ops).\n- **Placeholders** — `\"/slide[N]/placeholder[title]\"` / `placeholder[body]`. Available only when the slide uses a layout with placeholders (not `layout=blank`).\n- **Groups** (LEAD) — address children via `\"/slide[N]/group[@name=G]/shape[1]\"`. Survives reordering better than positional indexes.\n- **Zoom slide** (LEAD) — `--type zoom --prop target=N` (one link per target; alias `slide`). Emit N separate zoom shapes for a multi-target nav hub. Zoom is a runtime feature — `view html` shows the static geometry; the zoom interaction runs only in a live presentation viewer.\n- **Slide comments** — reviewer annotations anchored at `/slide[N]/comment[M]`. Full lifecycle (`add / set / get / query / remove`). Props: `text`, `author`, `initials` (auto-derived), `date` (ISO 8601, defaults to UtcNow), `x` / `y` (length anchor).\n  ```bash\n  officecli add \"$FILE\" \"/slide[2]\" --type comment --prop author=\"Alice\" --prop text=\"Tighten this bullet\" --prop x=20cm --prop y=3cm\n  officecli query \"$FILE\" 'comment' --json | jq '.data.results | length'   # count all review comments\n  officecli remove \"$FILE\" \"/slide[2]/comment[1]\"                           # resolve after addressing\n  ```\n\n### Deck-level recipes\n\nPatterns not obvious from the primitives. Each gives the **visual outcome** first, then a runnable block. `$FILE` = your filename. Use `/slide[last()]` to address the slide you just added. The recipes demonstrate **structure and coordinate math** — swap in the palette / fonts you chose for this topic; the navy `1E2761` + Georgia is just the example's theme, not a house style to copy verbatim.\n\n**Z-order.** Later-added shapes are on top. Add background decoration FIRST, titles LAST. To fix after the fact: `--prop zorder=back/front` (renumbers siblings — re-`get --depth 1` before stacking more).\n\n#### (a) Cover (and section divider)\n\n**Visual outcome.** Dark navy fill, centered 44pt title, 18pt ice-blue meta line.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=1E2761\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Strategic Growth Review\" \\\n  --prop x=2cm --prop y=7cm --prop width=29.87cm --prop height=3cm \\\n  --prop font=Georgia --prop size=44 --prop bold=true --prop color=FFFFFF --prop align=center\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Prepared for Acme Leadership — FY26 Outlook\" \\\n  --prop x=2cm --prop y=11cm --prop width=29.87cm --prop height=1.2cm \\\n  --prop font=Calibri --prop size=18 --prop color=CADCFC --prop align=center\n```\n\n**Section divider** = same cover, plus a giant translucent number (`size=120`, `opacity=0.15`) added FIRST so it sits behind the section title.\n\n#### (b) Data slide (chart + commentary block)\n\n**Visual outcome.** Left two-thirds: column chart with brand series colors. Right one-third: \"Key Insight\" card with 20pt heading + 18pt body — audience reads the takeaway before parsing the bars.\n\n```bash\nofficecli add \"$FILE\" / --type slide --prop layout=blank --prop background=FFFFFF\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"FY26 Revenue Beat Plan by 18%\" \\\n  --prop x=1.5cm --prop y=1cm --prop width=30cm --prop height=1.8cm \\\n  --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761\n\n# Chart — left 2/3 (single-quote the title because of `$`)\nofficecli add \"$FILE\" \"/slide[last()]\" --type chart --prop chartType=column \\\n  --prop series1.name=Actual --prop series1.values=\"42,45,48,55\" --prop series1.color=1E2761 \\\n  --prop series2.name=Plan --prop series2.values=\"40,42,45,48\" --prop series2.color=CADCFC \\\n  --prop categories=\"Q1,Q2,Q3,Q4\" --prop x=1.5cm --prop y=3.5cm --prop width=20cm --prop height=14cm --prop title='FY26 Revenue ($M)'\n\n# Commentary card — right 1/3: background + heading + body\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop preset=roundRect --prop fill=F5F7FA --prop line=none \\\n  --prop x=22.5cm --prop y=3.5cm --prop width=9.8cm --prop height=14cm\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"Key Insight\" \\\n  --prop x=23cm --prop y=4cm --prop width=9cm --prop height=1.2cm \\\n  --prop font=Georgia --prop size=20 --prop bold=true --prop color=1E2761\nofficecli add \"$FILE\" \"/slide[last()]\" --type shape --prop text=\"EMEA launch + NRR at 118% drove 12pp of the 18pp beat.\" \\\n  --prop x=23cm --prop y=5.5cm --prop width=9cm --prop height=11cm \\\n  --prop font=Calibri --prop size=18 --prop color=333333\n```\n\n#### (c) Flowchart / process diagram (boxes + connectors)\n\n**Visual outcome.** Four rounded boxes across at y=8cm, each 6×3cm, alternating navy/iceblue, joined by elbow connectors with triangle arrowheads.\n\nGrid math (4 boxes, 33.87cm slide, 1.5cm margins): `gap = (33.87 − 3 − 24) / 3 = 2.29cm`. x-positions: `1.5, 9.79, 18.08, 26.37`.\n\nEach box carries its own label via `valign=middle` (no separate overlay shape needed). Use `batch` heredoc for portable coordinate arithmetic — no `bc`, no bash arrays.\n\n```bash\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step1\",\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"1.5cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 1\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step2\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"9.79cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 2\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step3\",\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"18.08cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 3\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Step4\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"26.37cm\",\"y\":\"8cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Step 4\",\"font\":\"Georgia\",\"size\":\"20\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}}\n]\nEOF\n\n# Connector pattern — reuse for any box-to-box graph.\nfor pair in \"Step1 Step2\" \"Step2 Step3\" \"Step3 Step4\"; do\n  A=${pair% *}; B=${pair#* }\n  officecli add \"$FILE\" \"/slide[$SLIDE]\" --type connector \\\n    --prop \"from=/slide[$SLIDE]/shape[@name=$A]\" \\\n    --prop \"to=/slide[$SLIDE]/shape[@name=$B]\" \\\n    --prop shape=elbow --prop color=333333 --prop tailEnd=triangle\ndone\n```\n\n`shape=elbow` is canonical (`bentConnector2` / `bentConnector3` also accepted).\n\n#### (d) Multi-slide deck skeletons\n\nNo code block — it's a rhythm. The sequences below are **illustrations of one working cadence (alternating dark dividers with white content), not required running orders** — derive your actual arc from the content first (see \"Title sequence first\"), then borrow whatever divider/content rhythm fits:\n\n- **10-slide review:** Cover · Agenda · 3 KPI · Div01 · Chart · Chart · Div02 · Flow · Timeline · Close\n- **20-slide pitch:** same rhythm × 2, sectioned Problem · Solution · Market · Product · Traction · Model · Team · Financials · Ask\n- Every divider must appear **before** its section content (Gate 3 order sanity)\n- Cover/divider = (a); chart pages = (b); process pages = (c); KPI pages = (e); decision pages = (f)\n\n#### (e) KPI callouts — giant-number card grid\n\n**Visual outcome.** Three or four giant numbers across a row; each card = unit sublabel + small percent-change chip + one-line takeaway. The single most common exec-deck element.\n\n**Sizing rule.** 60pt Georgia bold fits ~5 chars in a 9.78cm card (`$84.2`, `118%`, `24.5`). For longer values (`$84.2M`), split: `$84.2` as the big number, `USD millions` as the sublabel — never shrink the font to chase a unit suffix, it just wraps.\n\nGrid math (3 cards, 1.5cm margins, 0.76cm gap): `col_width = (33.87 − 3 − 1.52) / 3 = 9.78cm`. x-positions: `1.5, 12.04, 22.58`. Use accent color on a single \"watch\" card so risk reads in one second.\n\n```bash\n# Two cards: navy standard + terracotta watch. Each = bg + big number + sublabel + chip.\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"preset\":\"roundRect\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"1.5cm\",\"y\":\"4cm\",\"width\":\"9.78cm\",\"height\":\"7cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"84.2\",\"x\":\"1.5cm\",\"y\":\"4.8cm\",\"width\":\"9.78cm\",\"height\":\"2.8cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"USD millions · ARR\",\"x\":\"1.5cm\",\"y\":\"8cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"color\":\"CADCFC\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"+24% YoY\",\"x\":\"1.5cm\",\"y\":\"9cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"CADCFC\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"preset\":\"roundRect\",\"fill\":\"B85042\",\"line\":\"none\",\"x\":\"22.58cm\",\"y\":\"4cm\",\"width\":\"9.78cm\",\"height\":\"7cm\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"\\$1.42\",\"x\":\"22.58cm\",\"y\":\"4.8cm\",\"width\":\"9.78cm\",\"height\":\"2.8cm\",\"font\":\"Georgia\",\"size\":\"60\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"CAC payback (yrs)\",\"x\":\"22.58cm\",\"y\":\"8cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"color\":\"FFFFFF\",\"align\":\"center\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"text\":\"+8% — watch\",\"x\":\"22.58cm\",\"y\":\"9cm\",\"width\":\"9.78cm\",\"height\":\"0.8cm\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\"}}\n]\nEOF\n```\n\n#### (f) Decision tree — YES/NO branching\n\n**Visual outcome.** Diamond at top-center; YES/NO child boxes diverging left-right; both converge into a shared terminal box. Layout: diamond at `x=13.94, y=2cm, 6×3cm`; YES at `3cm, 7.5cm`; NO at `22.87cm, 7.5cm`; terminal at `13.94cm, 13cm`. Convention: red = stop/escalate, blue = standard, green = safe terminal. **Every connector needs an arrowhead** — readers misparse direction otherwise.\n\n```bash\ncat <<EOF | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Decide\",\"preset\":\"diamond\",\"fill\":\"1E2761\",\"line\":\"none\",\"x\":\"13.94cm\",\"y\":\"2cm\",\"width\":\"6cm\",\"height\":\"3cm\",\"text\":\"Hazardous energy present?\",\"font\":\"Calibri\",\"size\":\"14\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"YesBox\",\"preset\":\"roundRect\",\"fill\":\"B85042\",\"line\":\"none\",\"x\":\"3cm\",\"y\":\"7.5cm\",\"width\":\"8cm\",\"height\":\"3cm\",\"text\":\"Lockout + Tagout + Verify\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"NoBox\",\"preset\":\"roundRect\",\"fill\":\"CADCFC\",\"line\":\"none\",\"x\":\"22.87cm\",\"y\":\"7.5cm\",\"width\":\"8cm\",\"height\":\"3cm\",\"text\":\"Proceed with standard PPE\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"1E2761\",\"align\":\"center\",\"valign\":\"middle\"}},\n  {\"command\":\"add\",\"parent\":\"/slide[$SLIDE]\",\"type\":\"shape\",\"props\":{\"name\":\"Done\",\"preset\":\"roundRect\",\"fill\":\"2C5F2D\",\"line\":\"none\",\"x\":\"13.94cm\",\"y\":\"13cm\",\"width\":\"6cm\",\"height\":\"2.5cm\",\"text\":\"Begin service\",\"font\":\"Calibri\",\"size\":\"16\",\"bold\":\"true\",\"color\":\"FFFFFF\",\"align\":\"center\",\"valign\":\"middle\"}}\n]\nEOF\n```\n\nThen 4 connectors (`Decide→YesBox`, `Decide→NoBox`, `YesBox→Done`, `NoBox→Done`) using the connector loop pattern from (c).\n\n## QA (Required)\n\n**Assume there are problems.** First render is almost never correct. If you found zero issues, you were not looking hard enough.\n\n### Delivery Gate (any failure = REJECT, do NOT deliver)\n\nGates 1–2b are text/schema-level (cannot see a rendered slide); Gate 3 is the only visual check. Done = every gate PASS **and** Gate 3 loop converged.\n\nEach gate is **run a command, judge its output** — the officecli commands are identical on every OS (macOS / Linux / Windows), so no shell scripting is needed; the judging is yours.\n\n- **Gate 1 — schema.** `officecli validate \"<file>\"`. Any schema error → REJECT and fix.\n- **Gate 2 — overflow / format / structure.** `officecli view \"<file>\" issues`. If it lists *any* issue (lines tagged `[O1]`, `[C1]`, `[S1]`, …) → REJECT, fix, re-run until clean.\n- **Gate 2b — leftover placeholders.** `officecli view \"<file>\" text`, then scan the output for `xxxx`, `lorem` / `ipsum`, `<TODO>`, `placeholder`, \"this slide layout\", or empty `()` / `[]`. Any hit → REJECT.\n\n### Gate 3 — Visual audit (MANDATORY)\n\nPick **one** path:\n\n**Screenshot (default)** — for vision-capable agents. Screenshot each slide in turn — `officecli view \"<file>\" screenshot --page 1 -o slide1.png`, then `--page 2`, … — until the page index runs past the deck (one screenshot = one slide). If it errors on page 1, use the fallback below.\n\n**Judge every PNG against the checklist, adversarially** — \"assume problems exist; finding none means you didn't look hard enough.\" Report one `slide N: <issue>` line per problem, or `PASS`. This step is required however you run it. **If** your harness can spawn a subagent, delegate the judging to a *fresh, independent* one — the agent that built the deck is biased toward \"looks fine\", a separate pair of eyes is more critical — handing it the screenshots + this checklist and the same adversarial framing. No subagent? Do exactly the same yourself.\n\n**Fallback — HTML-text** (no vision, or screenshot failed): read `view \"$FILE\" html` as text. DOM cannot prove **dark-on-dark / fine overlap / arrowheads / gap-margin metrics / column alignment** — flag these as \"not visually verified\" rather than PASS.\n\n**Optional `--grid N`** — only on user request for layout-rhythm, or when `view outline` shows anomalous layout distribution: `officecli view \"<file>\" screenshot --grid 3 -o grid.png`.\n\n**Per-slide checklist (assume issues exist):**\n\n- **overlap** — shapes / charts / giant decorative numbers (01/02/03 100pt+) colliding\n- **text overflow** — clipped at slide or shape boundary (KPI cards, narrow boxes)\n- **narrow text box** — content fits technically but wraps to many short lines (1–2 words each); long sublabel in a 3cm KPI card, body line in a too-tight column\n- **dark-on-dark** — fill brightness < 30% with text/icon brightness < 80% (incl. dark icons on dark without a contrasting circle)\n- **image treatment** — photo stretched/distorted, text raw on a busy image (no card/scrim), screenshot or logo cropped, transparent image floating on white\n- **missing arrowheads** — flowchart connectors as plain lines\n- **decorative-line / title mismatch** — accent bar sized for one-line title but title wrapped to two (or vice versa)\n- **footer / citation collision** — source line, page number, or footnote touching content above\n- **tight margin / gap** — element within ~0.5\" of slide edge, or two cards within ~0.3\"\n- **uneven gaps** — large empty area on one side, cramped on another (broken rhythm)\n- **column / repeat-element misalignment** — KPI cards / icons off baseline or inconsistent width\n- **order sanity** — sequence matches narrative (cover → agenda → dividers-before-sections → closing)\n\nREJECT with `slide N: <issue>` lines, else \"Gate 3 PASS\" (HTML-text fallback adds \"<unverified-items> not visually verified\").\n\n**Fix-verify (mandatory, max 3 cycles).** Fix → re-run Gate 3 → repeat until zero new issues; one fix often surfaces another. After 3 rounds without convergence, **stop** — likely seesaw, template-level cause, or agent misread. Report `slide N: <issue> — attempted: <fixes> — likely root: <template|design-conflict|ambiguous>` and let the user decide.\n\n**Then flush (part of the gate).** Once Gate 3 converges, end with `officecli save \"<file>\"` — this guarantees your edits are written to disk before delivery (use `officecli close \"<file>\"` instead to also release the resident on a one-shot handoff). Required final step, not optional. Always safe: never errors or loses work.\n\n## Common Pitfalls\n\nSanity-check cheatsheet — what breaks on the first try. Design + shell traps.\n\n| Pitfall | Correct approach |\n|---|---|\n| Unquoted `[N]` in zsh/bash | Always quote paths: `\"/slide[1]\"`. zsh globs unquoted `[1]` → `no matches found` — #1 first-use stumble |\n| `--name \"foo\"` | All attributes go through `--prop`: `--prop name=\"foo\"` |\n| `/shape[myname]` (bare name in brackets) | Use `@name=` selector: `/shape[@name=myname]` or `/shape[@id=10007]` |\n| Paths 1-based vs `--index` 0-based | `/slide[1]` = first slide; `--index 0` = first position |\n| `$` in `--prop text=` | Single-quote: `--prop text='$15M'`. Double-quoted `\"$15M\"` gets shell-expanded to `M` |\n| `\\n` / `\\t` in `--prop text=` | Interpreted by the CLI: `\\n` = paragraph break, `\\t` = tab. Double `\\\\n` for a literal |\n","frontmatter":{"name":"officecli-pptx","description":"Use this skill any time a .pptx file is involved -- as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file; editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions 'deck', 'slides', 'presentation', 'pitch', or references a .pptx filename."},"isInternal":false,"tokens":12279,"sizeBytes":44849},{"name":"SKILL.md","path":"skills/officecli-word-form/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-word-form/SKILL.md","title":"officecli-word-form","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-word-form\ndescription: \"Use this skill to create fillable Word forms (.docx) with real Content Controls (SDT) + legacy FormField checkboxes + MERGEFIELD mail-merge placeholders + document protection. Trigger on: 'fillable form', 'form fields', 'content controls', 'SDT', 'word form', 'fill in', 'only editable fields', 'protect document', 'onboarding form', 'HR intake', 'survey template', 'contract / SOW template', 'mail-merge template', 'compliance checklist', 'medical intake questionnaire'. Output is a single .docx where specific fields are editable and the rest is locked. This skill is INDEPENDENT, not a scene layer on docx — payload is `w:sdt` + `w:ffData` + `w:fldChar` + `documentProtection`, none of which docx base skill covers. Do NOT trigger for regular reports, letters, memos, academic papers, pitch decks, or any document with no user-fillable fields — route those to officecli-docx or its scene layers.\"\n---\n\n# OfficeCLI Word-Form Skill\n\n**This skill is INDEPENDENT, not a scene layer on docx.** A form's payload — `<w:sdt>` controls, `<w:ffData>` legacy fields, `<w:fldChar>` mail-merge, `documentProtection` — is a distinct element class from docx's paragraph/heading/style primitives. Its QA is different too: docx's Delivery Gate cares about visual layout and live PAGE fields, this skill's cares about data plumbing (protection enforced / alias+tag / items injected / name ≤ 20 / no underscore anti-pattern). **Reverse handoff:** if the user's document has no fillable fields (report, letter, memo, thesis, proposal), route to `officecli-docx` or a docx scene skill — don't use this one.\n\n## BEFORE YOU START (CRITICAL)\n\n**If `officecli` is not installed:**\n\n`macOS / Linux`\n\n```bash\nif ! command -v officecli >/dev/null 2>&1; then\n    curl -fsSL https://d.officecli.ai/install.sh | bash\nfi\n```\n\n`Windows (PowerShell)`\n\n```powershell\nif (-not (Get-Command officecli -ErrorAction SilentlyContinue)) {\n    irm https://d.officecli.ai/install.ps1 | iex\n}\n```\n\nVerify: `officecli --version`\n\nIf `officecli` is still not found after first install, open a new terminal and run the verify command again.\n\nIf the install command above fails (e.g. blocked by security policy, no network access, or insufficient permissions), install manually — download the binary for your platform from https://github.com/iOfficeAI/OfficeCLI/releases — then re-run the verify command.\n\n## Help-First Rule\n\nThis skill teaches what a real form needs, not every CLI flag. When a prop / alias / enum is uncertain, consult help BEFORE guessing: `officecli help docx [element] [--json]` (e.g. `sdt`, `formfield`, `field`). Help is pinned to the installed CLI version and is authoritative — when this skill and help disagree, **help wins** (the prop set on `sdt` in particular has grown over time; trust `help docx sdt`, not a hardcoded list).\n\n## Mental Model & Inheritance\n\nA Word form is a `.docx` plus four OpenXML payload layers plain-docx skills do not touch: **`<w:sdt>`** content controls (types: text / richtext / dropdown / combobox / date / picture / group), **`<w:ffData>`** legacy FormField (still the only way to get a real checkbox — SDT `type=checkbox` is not implemented), **`<w:fldChar>`** complex fields (MERGEFIELD, REF, PAGEREF, SEQ, IF — template-time, not user-fill), and **`documentProtection`** (the lock that makes non-field text read-only in Word — and, on the CLI, `protection=forms` locks non-field content edits (those need `--force` or `raw-set`) but still allows form-field (SDT) edits, which is the point of forms protection).\n\n**No inheritance from docx v2.** docx's Delivery Gate (cover-fill %, live-PAGE check) does NOT apply — form QA is `view forms` + `query sdt alias+tag` + `protectionEnforced`.\n\n**Reverse handoff to docx.** Route back to `officecli-docx` for reports / letters / memos / thesis / pitch decks / any document with no editable fields. Use **this** skill when the document's purpose is data capture or template merge.\n\n## Shell & Execution Discipline\n\n**One command at a time. Read output before the next.** OfficeCLI is incremental — every `add` / `set` / `remove` immediately mutates the file. All recipes below use `FILE=form.docx` as a shell variable.\n\n**Three shell-escape layers:**\n\n1. **Quote every path with `[N]`** — zsh/bash glob-expand brackets. `officecli get \"$FILE\" /body/sdt[1]` fails with `no matches found`. Correct: `officecli get \"$FILE\" '/body/sdt[1]'`.\n2. **Single-quote any prop containing `$`** — `\"Total: $50,000\"` becomes `\"Total: ,000\"` after `$50` variable expansion. Correct: `'Total: $50,000'`.\n3. **`--after find:<text>` uses outer single quotes, never inner double quotes** — `--after find:\"Client Signature:\"` makes the quotes part of the search string; match fails. Correct: `--after 'find:Client Signature:'`.\n\n**`WARNING: UNSUPPORTED` (exit 2) is a silently-wrong element.** The CLI created the element *without* the rejected prop (exit 2 = ran with caveats; only when *every* prop is refused does `--json` report `success: false`). Any UNSUPPORTED in your build log means a prop name the current CLI does not accept on that element — stop, check `help docx <element>` for the right prop name (most SDT props such as `items`/`format`/`lock` ARE accepted now; `maxlength` is not), fix the command, and re-run. Do not ship on top.\n\n**`protection=forms` is the LAST structural command.** Once it is set, `protection=forms` locks non-field content (those edits need `--force` or `raw-set`) but still allows form-field (SDT) edits — which is the point of forms protection. A non-field content edit (e.g. `set /body/p[N] --prop text=`) is refused (`ERROR: Document is protected … use --force`); a form-field edit (e.g. `set /body/sdt[N] --prop text=` / `--prop alias=`) succeeds (exit 0). Use `Query(\"editable\")` to find fillable fields. So finish all non-field (static layout) edits first, then lock; if you must edit static content afterward, pass `--force` (or temporarily clear protection, edit, re-lock).\n\n### `--after find:` micro-playbook\n\n`--after find:<text>` matches the **first** occurrence. Bad anchor = wrong insertion location, expensive to debug. Three rules:\n\n1. **Anchor must be globally unique.** In bilingual contracts \"甲方签字\" matches both parties — use a unique phrase like \"甲方签字（Service Provider）\" or full English title.\n2. **After insert, `/body/p[last()]` is unreliable** — the find insertion changes `<w:body>` child order. To continue operating on the new paragraph, read its real paraId: `officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId'`.\n3. **Chinese + full-width parens `（）`** match literally in `find`, but when unsure, `officecli view \"$FILE\" text | grep -n \"锚点\"` first to confirm the exact bytes in the file.\n\n```bash\n# Trap: first-match hits 甲方 only, 乙方 missed\nofficecli add \"$FILE\" /body --type sdt --after 'find:签字'\n\n# Fix: two signatories, two unique anchors\nofficecli add \"$FILE\" /body --type sdt --prop alias=Party_A_Name --prop tag=party_a \\\n  --after 'find:甲方签字（Service Provider）'\nPID_A=$(officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId')\nofficecli add \"$FILE\" \"/body/p[@paraId='$PID_A']\" --type sdt --prop alias=Party_A_Title --prop tag=party_a_title\n```\n\nInline SDT via `--after find:` is added as a child of the matched paragraph, not as a new paragraph — use this when label + SDT must share a line.\n\n## What makes a real form (identity)\n\nA real fillable form requires **structured fields** + **document protection**.\n\n| Approach | Word user sees | CLI-readable | Real form? |\n|---|---|---|---|\n| SDT controls + `protection=forms` | Gray-bordered fields; rest locked | `query sdt` / `view forms` | **YES** |\n| FormField checkbox + `protection=forms` | Real clickable checkbox; rest locked | `query formfield` / `view forms` | **YES** (checkbox only) |\n| MERGEFIELD placeholders | `«CustomerName»` merged by downstream engine | `query field` | **YES** (template-time) |\n| Underscores `___` / blank lines | Visual-only; whole doc editable | No — no structured fields | **NO** |\n\n**Do not simulate fields with underscores.** `姓名：_______________` produces zero structured data and leaks past every verification. Always use `--type sdt` or `--type formfield`.\n\n**Checkbox is formfield, NOT SDT.** `--type sdt --prop type=checkbox` exits 1 (`SDT type 'checkbox' is not implemented`). Every checkbox in every recipe uses `--type formfield --prop type=checkbox`.\n\n**MERGEFIELD is a separate track.** `view forms` lists SDT + formfield only; `query field` lists complex fields only. Two disjoint inventories; both valid in one file.\n\n## Requirements for Outputs (hard floor)\n\nEvery form must satisfy these — Delivery Gate enforces each as an executable check.\n\n1. `protection=forms` enforced (`get $FILE /` → `protectionEnforced=True`).\n2. Every SDT has both `alias` + `tag`.\n3. Every dropdown/combobox has non-empty `items=...` in `view forms`.\n4. Every date SDT shows the intended `format=...`.\n5. Every locked SDT shows `lock=sdtLocked` / `contentLocked` / `sdtContentLocked` as intended.\n6. Zero `WARNING: UNSUPPORTED` in build log.\n7. Zero `type=checkbox` on any SDT.\n8. Every formfield `name` ≤ 20 characters.\n9. Zero underscore-line / blank-line placeholders.\n10. Field types match user intent (short text / paragraph / fixed list / list+custom / date / boolean).\n\n## Three Paths (core decision)\n\nSDT props are **first-class on `add`** — confirm the current set with `officecli help docx sdt`. Today that includes `type, tag, alias, text, items, format, lock, placeholder/placeholderText, date.*` and the SDT types `text / richtext / dropdown / combobox / date / group / picture`. So most forms are pure `add` — no raw-set, no Word template. Two paths remain only for the genuinely-unreachable cases. **Pick the path before writing a single command.**\n\n### Path A — Pure CLI (the default for almost everything)\n\n**Use when**: any text / richtext / dropdown / combobox / date / picture / group SDT — including its options, date format, and lock. Pass the props straight on `add`; verify each persists with `get '/body/sdt[N]' --json`.\n\n```bash\n# dropdown WITH its options and a non-default date format, in one add each — no raw-set:\nofficecli add \"$FILE\" /body --type sdt \\\n  --prop type=dropdown --prop alias=\"Department\" --prop tag=dept \\\n  --prop items=\"Engineering,Finance,HR\"\nofficecli add \"$FILE\" /body --type sdt \\\n  --prop type=date --prop alias=\"Start Date\" --prop tag=start \\\n  --prop format=\"yyyy年MM月dd日\"\n# lock works on add AND set:\nofficecli add \"$FILE\" /body --type sdt --prop type=text --prop tag=full_name \\\n  --prop alias=\"Full Name\" --prop text=\"Enter full name\" --prop lock=sdtLocked\n# protection comes last, once all fields exist:\n# officecli set \"$FILE\" / --prop protection=forms\n```\n\n### Path B — CLI + `raw-set` bridge (only an attribute help does NOT expose)\n\n**Use when**: you need an SDT attribute that `help docx sdt` does not list (e.g. a `<w:listItem>` whose `w:value` must differ from its display text, or a sdtPr child with no `--prop`). `raw-set` is OfficeCLI's universal OpenXML fallback — `officecli --help` lists it as a top-level command. For ordinary dropdowns use `--prop items=` (Path A); raw-set here is the exception, not the rule.\n\n```bash\n# Display text ≠ stored value — not expressible via items=, so inject the listItems directly:\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown --prop alias=\"Department\" --prop tag=dept\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"ENG\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"FIN\"/>'\n```\n\n### Path C — Word template (only what no API reaches)\n\n**Use when**: a **real SDT checkbox** (`type=checkbox` still exits 1 — use a legacy FormField instead, see §Legacy FormField), a `placeholderDocPart` prompt-text part, or custom richtext appearance / cross-part nesting beyond `--prop` reach. Picture and grouped SDTs are NO LONGER here — they add fine via Path A.\n\n```bash\n# One-time in Word: Developer tab → Insert Content Control → Save as template.docx\ncp templates/onboarding_with_signature.docx \"$FILE\"\nofficecli open \"$FILE\"\nofficecli view \"$FILE\" forms                 # inspect embedded controls + paths\nofficecli set \"$FILE\" '/body/sdt[@sdtId=3]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" / --prop protection=forms\n```\n\n### Decision table\n\n| Need | Path | Note |\n|---|---|---|\n| text / richtext SDT with default string | **A** | `--prop type/alias/tag/text` |\n| text SDT that must be locked | **A** | `--prop lock=sdtLocked` works on `add` (and `set`) |\n| dropdown / combobox **with options** | **A** | `--prop items=\"A,B,C\"` |\n| date SDT with non-default format | **A** | `--prop format=\"yyyy年MM月dd日\"` |\n| signature picture SDT, grouped SDT | **A** | `--prop type=picture` / `type=group` add directly |\n| dropdown whose stored value ≠ display text | **B** | raw-set append `<w:listItem w:value=…>` |\n| real checkbox | **FormField** | `--type formfield --prop type=checkbox` (see §Legacy FormField) |\n| mail-merge placeholder | **MERGEFIELD** | `--type field --prop fieldType=mergefield` (see §MERGEFIELD) |\n| real SDT checkbox / placeholder part / custom appearance | **C** | build skeleton in Word, fill via CLI |\n\n## Quick Start — Path A + FormField (minimal intake form)\n\nTwo SDT text fields, one checkbox, protection. Paste and adapt; this is the smallest form worth shipping.\n\n```bash\nFILE=intake.docx\nofficecli close \"$FILE\" 2>/dev/null; rm -f \"$FILE\"   # preflight: clear stale resident / prior file (cold-start after CLI upgrade commonly leaks a resident)\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\n\nofficecli set \"$FILE\" / --prop title=\"Employee Onboarding Intake\" \\\n  --prop docDefaults.font=\"Calibri\" --prop docDefaults.fontSize=\"12pt\"\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Employee Onboarding Intake\" --prop style=Heading1 \\\n  --prop size=20 --prop bold=true --prop spaceAfter=18pt\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Full Name:\" --prop size=11 --prop bold=true --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Full Name\" --prop tag=full_name --prop text=\"Enter full name\"\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Start Date:\" --prop size=11 --prop bold=true --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Start Date\" --prop tag=start_date\n\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"Read and agree to employee handbook\" --prop size=11 --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type formfield \\\n  --prop type=checkbox --prop name=agree_handbook --prop checked=false\n\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\nofficecli view \"$FILE\" forms\n```\n\n## Path B — raw-set recipes\n\nThree recipes cover almost every complex-attr need on SDT forms.\n\n### B1 — Dropdown items (append)\n\n```bash\n# Skeleton (Path A)\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown \\\n  --prop alias=\"Department\" --prop tag=dept\n\n# Inject items\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"Engineering\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"Finance\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"HR\" w:value=\"HR\"/>'\n\n# Verify\nofficecli get \"$FILE\" '/body/sdt[1]'   # expect: type=dropdown items=Engineering,Finance,HR\n```\n\n**Template.** Swap `<TAG>` / `<LABEL>` / `<VALUE>` only. `xmlns:w=...` is required on every root `<w:listItem>` — raw-set does not inherit namespace prefixes. Chain multiple `<w:listItem>`s in one call; option order is preserved.\n\n### B2 — Combobox items (same as B1, different xpath tail)\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=combobox \\\n  --prop alias=\"Current Medication\" --prop tag=current_med\n\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='current_med']/w:sdtPr/w:comboBox\" \\\n  --action append \\\n  --xml '<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Antihypertensives\" w:value=\"Antihypertensives\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Insulin\" w:value=\"Insulin\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Other (specify)\" w:value=\"Other\"/>'\n```\n\nOnly difference from B1: `w:comboBox` vs `w:dropDownList` in the xpath tail. Combobox lets the user type custom input; dropdown does not.\n\n### B3 — Date format (setattr)\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Contract Start Date\" --prop tag=contract_start\n\n# Chinese: yyyy年MM月dd日\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:sdt[w:sdtPr/w:tag/@w:val='contract_start']/w:sdtPr/w:date/w:dateFormat\" \\\n  --action setattr \\\n  --xml \"w:val=yyyy年MM月dd日\"\n\n# US:    w:val=MM/dd/yyyy\n# ISO:   w:val=yyyy-MM-dd  (already the default)\n# Long:  w:val=\"MMMM d, yyyy\"\n\nofficecli get \"$FILE\" '/body/sdt[N]'   # expect: type=date format=yyyy年MM月dd日\n```\n\n`setattr` replaces one attribute — do not quote the value inside `--xml`. Only `w:val` is touched; the `<w:dateFormat>` wrapper is preserved.\n\n### raw-set actions & errors\n\n| `--action` | Form use |\n|---|---|\n| `append` | Insert new child at end of target (B1, B2 — listItem) |\n| `setattr` | Change one attribute; `--xml \"key=value\"` (B3 — dateFormat/@val) |\n| `replace` | Replace entire target (rare — reset a full `<w:date>` wrapper) |\n| `remove` | Delete the target (clear options before re-populate) |\n\n| Symptom | Fix |\n|---|---|\n| `raw-set: 0 element(s) affected` | XPath did not match. Check the `tag` value and whether the SDT is block or inline. Fall back to `officecli raw $FILE /document` to read the real XML. |\n| `Error: prefix 'w' is not defined` | Missing `xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"` on the fragment — every root element in `--xml` needs it. |\n| Items readback empty after append | `<w:dropDownList/>` must already exist (Path A `type=dropdown` ensures this). If absent, append has nowhere to insert. |\n| `VALIDATION: N new error(s) introduced` on same line as success | Your append introduced a schema-invalid child. Treat as stop-and-fix even though `raw-set` exits 0. |\n\n## Path C — Word template workflow\n\nFor fields CLI cannot express (signature `picture` SDT, real SDT checkbox, `placeholderDocPart` prompt text, grouped SDTs, custom richtext styling), build the skeleton once in Word, then fill via CLI.\n\n**One-time in Word:** File → Options → Customise Ribbon → Developer. Developer tab → Insert Picture / Check Box / Grouping Content Control → right-click → Properties → set Title (`alias`) + Tag. Save as `template.docx`.\n\n**Fill via CLI:**\n\n```bash\ncp templates/onboarding_with_signature.docx \"$FILE\"\nofficecli open \"$FILE\"\nofficecli view \"$FILE\" forms                                    # see /body/... paths + sdtId values\nofficecli set \"$FILE\" '/body/sdt[@sdtId=3]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\n```\n\n## MERGEFIELD (data-driven track)\n\n`help docx field` declares a `fieldType` enum of ~30 values including `mergefield`, `ref`, `pageref`, `seq`, `if` — all CLI-expressible with their typed props. MERGEFIELD coexists with SDT in the same file but is reported by `query field` only; `view forms` does NOT list MERGEFIELDs (they are not user-fillable).\n\n**Canonical MERGEFIELD:**\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Dear \"\nofficecli add \"$FILE\" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CustomerName\nofficecli add \"$FILE\" '/body/p[1]' --type run --prop text=\", \"\nofficecli add \"$FILE\" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CompanyName\n# Readback: \"Dear «CustomerName», «CompanyName»\"\n```\n\n**Element-type shortcut** (equivalent): `officecli add \"$FILE\" '/body/p[1]' --type mergefield --prop name=CustomerName`.\n\n### Common field patterns\n\n| Pattern | Call shape |\n|---|---|\n| Mail-merge placeholder | `--type field --prop fieldType=mergefield --prop name=<FieldName>` |\n| Mail-merge with numeric picture (money, percent) | `--type field --prop fieldType=mergefield --prop name=Amount --prop instr='MERGEFIELD Amount \\# \"#,##0.00\"'`. The typed `format` prop is ignored for mergefield (prints a warning) — use `instr` (alias `instruction`) to embed the full field code. Verify: `query \"$FILE\" field --json \\| jq '.data.results[].format.instruction'` must contain `\\#` and the picture. |\n| Mail-merge with date picture | `--type field --prop fieldType=mergefield --prop name=StartDate --prop instr='MERGEFIELD StartDate \\@ \"yyyy-MM-dd\"'` |\n| Cross-reference to bookmark text | `--type field --prop fieldType=ref --prop name=<BookmarkName>` |\n| Cross-reference to bookmark's page number | `--type field --prop fieldType=pageref --prop name=<BookmarkName>` |\n| Auto-numbering (Figure 1 / 2 / 3) | `--type field --prop fieldType=seq --prop identifier=Figure` |\n| Page number in footer | `--type field --prop fieldType=page` |\n| \"Page X of Y\" | two fields: `fieldType=page` + `fieldType=numpages` |\n| Conditional text | `--type field --prop fieldType=if --prop expression='{ MERGEFIELD Gender } = \"Male\"' --prop trueText=\"Mr.\" --prop falseText=\"Ms.\"` |\n\n### IF conditional (CLI-expressible)\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"\"\nofficecli add \"$FILE\" '/body/p[last()]' --type field --prop fieldType=if \\\n  --prop expression='{ MERGEFIELD Gender } = \"Male\"' \\\n  --prop trueText=\"Mr.\" --prop falseText=\"Ms.\"\nofficecli add \"$FILE\" '/body/p[last()]' --type run --prop text=\" \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field --prop fieldType=mergefield --prop name=LastName\n# Merge-time result: \"Mr. «LastName»\" or \"Ms. «LastName»\"\n```\n\nNested wrappers like `{ IF { MERGEFIELD X } = \"Y\" { REF bm } \"fallback\" }` are not expressible via `--prop` chaining — drop to raw-set a hand-crafted `<w:fldChar>` / `<w:instrText>` fragment, or build once in a Word template (Path C).\n\n**Readback.** `query $FILE field` lists `/field[N]` + instruction + `fieldType`. `view $FILE forms` does NOT list MERGEFIELDs (only SDT + formfield) — they are template-time, not end-user fillable. `get $FILE '/body/p[1]'` renders the guillemet-wrapped field name.\n\n## Legacy FormField\n\nUse FormField **when you need a real checkbox**. For text/dropdown, prefer SDT.\n\n`help docx formfield`: `type` (text/checkbox/check/dropdown), `name` (required, **≤ 20 chars** — OpenXML schema MaxLength; add passes longer but `validate` rejects), `text` (text only, alias `value`), `checked` (checkbox only).\n\n```bash\n# CHECKBOX — the only real checkbox (SDT type=checkbox is not implemented)\nofficecli add \"$FILE\" /body --type formfield --prop type=checkbox \\\n  --prop name=agree_terms --prop checked=false\n\n# TEXT formfield\nofficecli add \"$FILE\" /body --type formfield --prop type=text \\\n  --prop name=emp_name --prop text=\"Enter name\"\n\n# DROPDOWN formfield — items NOT settable via CLI; use Word template or SDT Path B\nofficecli add \"$FILE\" /body --type formfield --prop type=dropdown --prop name=dept_select\n\n# Read / modify by name (stable) or 1-based index\nofficecli get \"$FILE\" '/formfield[agree_terms]'\nofficecli set \"$FILE\" '/formfield[agree_terms]' --prop checked=true\nofficecli set \"$FILE\" '/formfield[emp_name]' --prop text=\"Jane Smith\"\nofficecli set \"$FILE\" '/formfield[dept_select]' --prop text=\"Engineering\"\n```\n\nFormField paths (`/formfield[N]` or `/formfield[<name>]`) are separate from SDT paths (`/body/sdt[N]`). Both coexist; `protection=forms` covers both.\n\n**Scale.** Tested with 50+ checkboxes in a single document — no practical cap on formfield count; build and `validate` remain clean. `name` ≤ 20 chars (K13) is the only hard constraint.\n\n**Renderer note — formfield checkbox `[RENDERER-BUG]`.** LibreOffice's PDF export occasionally renders the formfield checkbox as `☐☐` (doubled box). Word and WPS render a single clickable box (toggles ☑). This is a LibreOffice renderer quirk, **not a skill or document quality issue** — see K19. Do not attempt workarounds in the form; if an evaluator screenshots a LibreOffice-generated PDF and sees `☐☐`, attribute to `[RENDERER-BUG]`.\n\n## Document protection & lock\n\n### Enabling form protection\n\n```bash\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli get \"$FILE\" /                                  # look for: protectionEnforced=True\n```\n\n### Protection modes\n\n| Mode | Word user can | CLI behavior |\n|---|---|---|\n| `forms` | Fill SDT + formfield only | Form-field (SDT) edits work (exit 0); non-field content edits need `--force` or `raw-set` |\n| `readOnly` | Read only | Non-field edits need `--force`; raw-set bypasses |\n| `comments` | Add comments only | Non-field edits need `--force`; raw-set bypasses |\n| `trackedChanges` | Edit with tracked changes only | Non-field edits need `--force`; raw-set bypasses |\n| `none` | Full editing | All ops work |\n\n**KEY:** Document protection restricts Word users AND the CLI. Under `protection=forms`, form-field (SDT) edits — `set /body/sdt[N] --prop text=` / `--prop alias=` — succeed (exit 0); this is the point of forms protection. Only NON-field content edits (e.g. `set /body/p[N] --prop text=`) are refused with `ERROR: Document is protected (mode: forms). … use --force to override`; pass `--force` (or `raw-set`, the only verb that fully bypasses protection) to edit static content anyway. Use `Query(\"editable\")` to find the fields a Word user could still fill.\n\n### Lock values (settable on `add` and `set`)\n\n```bash\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked           # content editable; control cannot be deleted\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=contentlocked       # content read-only; control can be deleted\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtcontentlocked    # both locked\n# Omit lock entirely → unlocked (default)\n```\n\n`--prop lock=...` works on `add` as well as `set` — set it inline when you create the control. Readback normalises to camelCase (`sdtLocked`) regardless of input case — both accepted.\n\n### lock × `protection=forms` interaction\n\n| lock value | `protection=forms` active | Word user can edit? | Word user can delete control? |\n|---|---|---|---|\n| (none) | yes | **Yes** | **Yes** |\n| `sdtlocked` | yes | Yes | No |\n| `contentlocked` | yes | No | Yes |\n| `sdtcontentlocked` | yes | No | No |\n| block-level SDT wrap `contentlocked` | any | No (wrapped paragraph read-only regardless of protection) | No |\n| any | `readOnly` mode | No | No |\n\n### Block-level lock (paragraph-wrapping SDT)\n\n`protection=forms` is document-level — once an admin unprotects, every static paragraph (disclaimer, legal attestation, contract clause) becomes editable again. Master templates need defense-in-depth: wrap the critical paragraph in a block-level `<w:sdt>` with `lock=contentLocked`, so the content stays read-only even after protection is stripped.\n\n```bash\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"I authorize the above and acknowledge all clauses.\" --prop size=11 --prop spaceAfter=12pt\nPID=$(officecli query \"$FILE\" paragraph --json | jq -r '.data.results[-1].format.paraId')\n\n# raw-set actions: append | prepend | insertbefore | insertafter | replace | remove | setattr\n# No `wrap` action — two-step instead: (1) insertbefore an empty <w:sdt><w:sdtContent/></w:sdt>,\n# (2) move the original <w:p> inside by `replace` on the sdtContent with a copy of the paragraph XML.\n# Simpler alternative: read the paragraph XML via `officecli raw`, then `replace` the whole <w:p> with <w:sdt>...<w:sdtContent>[original w:p]</w:sdtContent></w:sdt>:\n# NOTE: this needs an XML-aware extraction, NOT awk. `raw /document` emits the whole document on ONE line,\n# so an awk range like `/paraId=.../,/<\\/w:p>/` grabs the entire <w:document> (schema-invalid). Parse the XML\n# and pull the single <w:p> by paraId instead:\nPARA_XML=$(officecli raw \"$FILE\" /document | python3 -c '\nimport sys, xml.etree.ElementTree as ET\nxml = sys.stdin.read(); pid = sys.argv[1]\nns = {\"w\":\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\"w14\":\"http://schemas.microsoft.com/office/word/2010/wordml\"}\nfor p, u in ns.items(): ET.register_namespace(p, u)\nroot = ET.fromstring(xml); key = \"{%s}paraId\" % ns[\"w14\"]\nfor p in root.iter(\"{%s}p\" % ns[\"w\"]):\n    if p.attrib.get(key) == pid:\n        sys.stdout.write(ET.tostring(p, encoding=\"unicode\")); break\n' \"$PID\")\nofficecli raw-set \"$FILE\" /document \\\n  --xpath \"//w:p[@w14:paraId='$PID']\" \\\n  --action replace \\\n  --xml \"<w:sdt xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" xmlns:w14=\\\"http://schemas.microsoft.com/office/word/2010/wordml\\\"><w:sdtPr><w:alias w:val=\\\"Authorization\\\"/><w:tag w:val=\\\"auth_para\\\"/><w:lock w:val=\\\"contentLocked\\\"/></w:sdtPr><w:sdtContent>${PARA_XML}</w:sdtContent></w:sdt>\"\n```\n\nVerify with `query sdt --json | jq '.data.results[] | select(.format.lock == \"contentLocked\" and .format.type == \"block\")'`. Use only for legal attestations, compliance disclaimers, confidentiality clauses — regular intake fields do not need this.\n\n### Role-gated fields (multi-role forms)\n\nWhen one form is filled by two roles (patient vs physician; Party A vs Party B), use `lock=contentLocked` on the fields the other role must not touch. Under `protection=forms`, `contentLocked` SDTs display as read-only in Word; the intended role unprotects (or the admin swaps role-specific copies) to fill the other half.\n\n```bash\n# Patient section — editable (no lock, or sdtlocked to prevent accidental deletion only)\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked      # patient_name\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked      # patient_dob\n\n# Physician section — locked against patient edits\nofficecli set \"$FILE\" '/body/sdt[14]' --prop lock=contentLocked # physician_diagnosis\nofficecli set \"$FILE\" '/body/sdt[15]' --prop lock=contentLocked # physician_signature\n```\n\nThis is the core pattern for medical intake, two-party contracts, sequential-approval forms.\n\n## Recipe — Contract / SOW template with MERGEFIELD + signature\n\nRow-map across the three sub-recipes: SDT[1]=project_name, SDT[2]=contract_start, SDT[3]=payment_schedule, SDT[4]=signatory_name (inline). Run (sow-a) → (sow-b) → (sow-c) in order on the same `$FILE`; each sub-recipe stays under 20 lines so a shell-escape slip never cascades past one block.\n\n### Recipe (sow-a) Boilerplate + cover + parties\n\nCreates the file, sets docDefaults, writes the title / intro, and drops the two MERGEFIELD placeholders (`CustomerName`, `ContractNo`) that downstream mail-merge will fill.\n\n```bash\nFILE=sow.docx\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" / --prop title=\"Statement of Work\" \\\n  --prop docDefaults.font=\"Calibri\" --prop docDefaults.fontSize=\"12pt\"\n\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Statement of Work\" \\\n  --prop style=Heading1 --prop size=20 --prop bold=true --prop spaceAfter=12pt\nofficecli add \"$FILE\" /body --type paragraph \\\n  --prop text=\"This Statement of Work ('SOW') is entered into between the parties identified below and governs the delivery of professional services.\" \\\n  --prop size=11 --prop spaceAfter=12pt\n\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Customer: \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field \\\n  --prop fieldType=mergefield --prop name=CustomerName\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Contract #: \"\nofficecli add \"$FILE\" '/body/p[last()]' --type field \\\n  --prop fieldType=mergefield --prop name=ContractNo\n```\n\n### Recipe (sow-b) SDT fields — all via Path A\n\nAdds the three block-level SDTs (project / date / dropdown) and the inline signature SDT anchored via `--after 'find:Client Signature:'`. The date format and dropdown items go straight on `add` — no raw-set needed.\n\n```bash\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Project Name\" --prop tag=project_name --prop text=\"Enter project name\"\nofficecli add \"$FILE\" /body --type sdt --prop type=date \\\n  --prop alias=\"Contract Start Date\" --prop tag=contract_start --prop format=\"MM/dd/yyyy\"\nofficecli add \"$FILE\" /body --type sdt --prop type=dropdown \\\n  --prop alias=\"Payment Schedule\" --prop tag=payment_schedule \\\n  --prop items=\"Full Prepayment,Net 30 Upon Delivery\"\nofficecli add \"$FILE\" /body --type paragraph --prop text=\"Client Signature:\" \\\n  --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=4pt\nofficecli add \"$FILE\" /body --type sdt --prop type=text \\\n  --prop alias=\"Signatory Name\" --prop tag=signatory_name --prop text=\"Authorized Signatory\" \\\n  --after 'find:Client Signature:'\n# (Only reach for raw-set if a listItem's stored value must differ from its display text — see Path B.)\n```\n\n### Recipe (sow-c) Watermark + locks + document protection\n\nDrops the CONFIDENTIAL watermark (parent is `/`, never `/body`), locks the three block-level SDTs, instructs how to lock the inline signatory_name SDT (path only known after `view forms`), then seals the document with `protection=forms` as the last command.\n\n```bash\nofficecli add \"$FILE\" / --type watermark \\\n  --prop text=\"CONFIDENTIAL\" --prop color=FF0000 --prop rotation=315\n\nofficecli set \"$FILE\" '/body/sdt[1]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[2]' --prop lock=sdtlocked\nofficecli set \"$FILE\" '/body/sdt[3]' --prop lock=sdtlocked\nofficecli view \"$FILE\" forms   # copy signatory_name path, then: set '/body/p[@paraId=...]/sdt[1]' --prop lock=sdtlocked\n\nofficecli set \"$FILE\" / --prop protection=forms\nofficecli close \"$FILE\"\nofficecli query \"$FILE\" field     # expect 2 MERGEFIELDs: CustomerName, ContractNo\n```\n\n## Design principles (forms)\n\n**Control-type decision tree:**\n\n```\nDate → type=date | Fixed list → type=dropdown | List + custom → type=combobox\nShort text → type=text | Long text → type=richtext | Boolean → formfield checkbox\n```\n\n**Typography scale.** Spacing unit trap: `spaceBefore` / `spaceAfter` / `spaceLine` default to **twips** (1/20 pt) — always write `spaceBefore=18pt`.\n\n| Element | Size | Style | Spacing |\n|---|---|---|---|\n| Form title (H1) | 20pt | Bold | `spaceBefore=0pt`, `spaceAfter=12pt` |\n| Section heading (H2) | 14pt | Bold | `spaceBefore=18pt`, `spaceAfter=8pt` |\n| Field label | 11pt | Bold | `spaceAfter=4pt` |\n| Instructions / notes | 11pt | Italic `color=666666` | `spaceAfter=18pt` |\n\n**Accessibility bump.** For medical / geriatric / accessibility-focused forms, raise field label + instruction to **12pt** (11pt default is tight for older users); keep section headings at 14pt.\n\n**CJK forms:** set `docDefaults.font=\"Microsoft YaHei\"` — Calibri lacks Chinese glyphs.\n\n**Field ordering.** (1) Personal / ID, (2) role / classification, (3) dates, (4) supplemental free-text, (5) confirmation / signature.\n\n**Yes/No + conditional follow-up** (common in compliance / medical intake): formfield checkbox followed by a richtext SDT whose `alias` carries the cue — e.g. `--type formfield --prop type=checkbox --prop name=has_cond` then `--type sdt --prop type=richtext --prop alias=\"If yes, explain\" --prop tag=cond_detail --prop text=\"If yes, explain here\"`.\n\n**Signature block order.** Label on its own paragraph, SDT on the next paragraph (with `spaceBefore=18pt` on the label, `spaceAfter=4pt` on the SDT). Never `Label: SDT` inline — Word renders the runs as touching, visually stuck together.\n\n**Build order.** create+open → metadata → structure (headings, label paragraphs) → SDT/formfield skeletons (Path A 4 props) → Path B injections → per-field lock → `protection=forms` LAST → close.\n\n**Header / footer note.** Headers/footers are **predefined** when the section is created (default/first/even, 3 each). The first mutation must be `set` against the existing part, not `add` — `add $FILE /header ...` returns `already exists` or silently no-ops. Inspect first with `officecli query \"$FILE\" header --json` to read the `type` values, then `officecli set \"$FILE\" '/header[@type=default]' --prop text=...`. Only use `add` when creating an additional section with its own header/footer.\n\n## Batch mode (brief)\n\nFor forms with many controls, batch reduces overhead. Path A + Path B coexist in one batch.\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/body\",\"type\":\"sdt\",\"props\":{\"type\":\"text\",\"alias\":\"Full Name\",\"tag\":\"full_name\",\"text\":\"Enter name\"}},\n  {\"command\":\"add\",\"parent\":\"/body\",\"type\":\"sdt\",\"props\":{\"type\":\"dropdown\",\"alias\":\"Department\",\"tag\":\"dept\"}},\n  {\"command\":\"raw-set\",\"part\":\"/document\",\"xpath\":\"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList\",\"action\":\"append\",\"xml\":\"<w:listItem xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" w:displayText=\\\"Engineering\\\" w:value=\\\"Engineering\\\"/><w:listItem xmlns:w=\\\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\\\" w:displayText=\\\"Finance\\\" w:value=\\\"Finance\\\"/>\"},\n  {\"command\":\"set\",\"path\":\"/body/sdt[1]\",\"props\":{\"lock\":\"sdtlocked\"}},\n  {\"command\":\"set\",\"path\":\"/body/sdt[2]\",\"props\":{\"lock\":\"sdtlocked\"}}\n]\nEOF\nofficecli set \"$FILE\" / --prop protection=forms\n```\n\n- Escape inner `\"` in `xml` with `\\\"`. Use single-quoted heredoc `<<'EOF'` so `$var` does not expand.\n- **P0 batch trap:** genuinely-unsupported props in batch are silently dropped, **no WARNING** (interactive `add` would print WARNING: UNSUPPORTED, exit 2). Defence: send only props `help docx sdt` lists (`type/tag/alias/text/items/format/lock/...` are fine on `add`; `maxlength` is not), and verify with a readback after the batch.\n- `batch` supports `add`, `set`, `get`, `query`, `remove`, `validate`, `raw-set`.\n\n## Delivery Gate (executable)\n\nRun every gate below after every form. Each gate must print its `OK` line. Any `REJECT` = do not deliver.\n\n```bash\n# Assumes FILE=<your-form.docx>, document has been closed with officecli close \"$FILE\"\n\n# Gate 1 — Validate (must be clean; protection=forms no longer produces a schema error)\nVAL_OUT=$(officecli validate \"$FILE\" 2>&1)\nVAL_ERRS=$(echo \"$VAL_OUT\" | grep -c '\\[Schema\\]')\nif [ \"$VAL_ERRS\" -eq 0 ]; then echo \"Gate 1 OK (validate clean)\"\nelse echo \"REJECT Gate 1: $VAL_ERRS schema errors\"; echo \"$VAL_OUT\"; exit 1\nfi\n\n# Gate 2 — Token / placeholder leak (labels used as visual underscore substitutes)\nLEAK=$(officecli view \"$FILE\" text | grep -niE '_{3,}|TBD|\\(fill in\\)|\\{\\{|xxxx|lorem|placeholder')\n[ -z \"$LEAK\" ] && echo \"Gate 2 OK (no underscore / placeholder leak)\" || { echo \"REJECT Gate 2:\"; echo \"$LEAK\"; exit 1; }\n\n# Gate 3 — At least one structured field exists\nSDT_N=$(officecli query \"$FILE\" sdt --json | jq '.data.results | length')\nFF_N=$(officecli query \"$FILE\" formfield --json | jq '.data.results | length')\nFLD_N=$(officecli query \"$FILE\" field --json | jq '.data.results | length')\nTOTAL=$((SDT_N + FF_N + FLD_N))\n[ \"$TOTAL\" -gt 0 ] && echo \"Gate 3 OK ($SDT_N sdt + $FF_N formfield + $FLD_N field)\" || { echo \"REJECT Gate 3: 0 structured fields — this is not a form\"; exit 1; }\n\n# Gate 4 — Every SDT has alias + tag (skill-imposed H2)\n# NOTE: `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` — use `.data.results[0].format.alias` / `.format.tag`, never bare `.alias` or `.data.format`.\nSDT_MISSING=$(officecli query \"$FILE\" sdt --json | jq '[.data.results[] | select(.format.alias == null or .format.alias == \"\" or .format.tag == null or .format.tag == \"\")] | length')\n[ \"$SDT_MISSING\" -eq 0 ] && echo \"Gate 4 OK (every SDT has alias+tag)\" || { echo \"REJECT Gate 4: $SDT_MISSING SDT(s) missing alias or tag\"; exit 1; }\n\n# Gate 5 — Protection enforced + per-field lock inventory\nPROT=$(officecli get \"$FILE\" / --json | jq -r '.data.results[0].format.protection // \"none\"')\n[ \"$PROT\" = \"forms\" ] && echo \"Gate 5 OK (protection=forms enforced)\" || { echo \"REJECT Gate 5: protection is '$PROT', expected 'forms'\"; exit 1; }\nofficecli view \"$FILE\" forms | head -40   # visual spot-check: every dropdown shows items=; every date shows format=; every locked SDT shows lock=\n\n# Gate 6 — No type=checkbox leaked onto any SDT\nBAD_CB=$(officecli query \"$FILE\" sdt --json | jq '[.data.results[] | select(.format.type == \"checkbox\")] | length')\n[ \"$BAD_CB\" -eq 0 ] && echo \"Gate 6 OK (no SDT checkbox — formfield only)\" || { echo \"REJECT Gate 6: $BAD_CB SDT with type=checkbox\"; exit 1; }\n```\n\n**Why `view issues` is not a gate.** It runs only prose-style checks (first-line-indent, heading size) and flags every form label as `Body paragraph missing first-line indent` — a false-positive avalanche on forms. Ignore for this skill. Use `validate` (schema integrity) and `view forms` (field inventory).\n\n## Known Issues\n\n| # | Issue | Behavior | Workaround |\n|---|---|---|---|\n| K1 | SDT `type=checkbox` not implemented | `add ... --type sdt --prop type=checkbox` → `Error: SDT type 'checkbox' is not implemented`, exit 1 (error now lists the supported types incl. group/picture) | Use `--type formfield --prop type=checkbox` |\n| K3 | SDT `maxlength` UNSUPPORTED on `add` | `WARNING: UNSUPPORTED: maxlength`, exit 2; element still created. (`items` / `format` / `lock` / `placeholderText` are NOW supported on `add` — only `maxlength` is rejected; `name` is accepted but a no-op on SDT — use `alias`/`tag`) | Enforce length downstream; use `text` for initial content |\n| K4 | SDT `items` / `format` / `type` not settable AFTER creation | `set --prop items=...` → `UNSUPPORTED props (use raw-set instead)`. (They ARE settable on `add` — set them inline at creation) | Set at `add` time; to change later, Path B `raw-set` or `remove` + re-add |\n| K5 | FormField `maxlength` UNSUPPORTED | `WARNING: UNSUPPORTED: maxlength`; formfield created | Enforce length in downstream validation |\n| K6 | FormField dropdown `items` UNSUPPORTED | Dropdown formfield is created with empty option list | Use an SDT dropdown with `--prop items=` instead |\n| K7 | Watermark `width` / `height` not settable | Watermark created without them; `opacity` IS settable on `add` and reads back (real dims are computed) | For an exact size, open Word + adjust the shape (Phase 2) |\n| K9 | Batch mode silently drops genuinely-UNSUPPORTED props | No `WARNING` line; batch reports \"N succeeded\" even when a prop (e.g. `maxlength`) was dropped | Keep batch SDT entries to props `help docx sdt` lists; verify with a readback after the batch |\n| K13 | FormField `name` > 20 characters | `add` returns exit 0 with no warning; `validate` later reports `[Schema] ... MaxLength=20` on `/w:ffData/w:name` | Keep `name` ≤ 20 characters (OpenXML schema limit). SDT `alias` / `tag` have no such limit |\n| K14 | `shd.fill` on a paragraph emits schema-invalid `<w:pPr>/<w:shd>` | `validate` reports 2 schema errors per instance (`unexpected child element`, `required attribute 'val' missing`); Word renders it anyway | Apply highlight on the run instead (`shading=HEX`, flat canonical), or raw-set `<w:shd w:val=\"clear\" w:fill=\"HEX\"/>` into the run's `<w:rPr>` |\n| K15 | `view forms` does NOT list MERGEFIELDs | Only SDT + formfield in output; MERGEFIELDs are template-time, not end-user fillable | Treat `query field` and `view forms` as two disjoint inventories. Every recipe verifies both |\n| K16 | Header / footer are predefined at section creation (default/first/even, 3 each) | `add $FILE /header ...` returns `already exists` or silently no-ops on the first call | First mutation uses `set` against the existing part: `officecli query $FILE header --json` to read `type`, then `set '/header[@type=default]' --prop text=...`. Only use `add` for a brand-new section's header/footer |\n| K17 | Watermark injected into header emits `<w:noProof>` child that is schema-invalid | `validate` adds an extra `[Schema]` error at `/header[N]/w:sdt/.../w:noProof` — a real schema error to fix, not a benign one | After `add $FILE / --type watermark`, run once per header part: `officecli raw-set $FILE /word/header1.xml --xpath \"//w:noProof\" --action remove` (repeat for `header2.xml`, `header3.xml` if present) |\n| K18 | `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` | Writing jq against bare `.alias` / `.tag` / `.protection`, or `.data.format.*`, returns null/0 matches and Gate 4/5 falsely report \"missing=N\" | Use `.data.results[].format.alias` / `.format.tag`; for `get /` use `.data.results[0].format.protection` (NOT `.data.format.protection`). Same for `.format.type` / `.format.paraId` |\n| K19 | LibreOffice renders formfield checkbox as `☐☐` (double box) in PDF export | Cosmetic only — Word / WPS render a single box, clickable to toggle ☑. A LibreOffice renderer quirk, flagged as [RENDERER-BUG] | Do not try to \"fix\" in the skill. If an evaluator screenshots from LibreOffice-generated PDF and sees `☐☐`, attribute to [RENDERER-BUG], not a form-quality defect |\n\n## Phase 2 — enhance in Word\n\nSome polish is out of CLI scope. Hand the file to a human for these; none are required for a valid form.\n\n| Need | Why open Word |\n|---|---|\n| Real SDT checkbox with specific locking | `type=checkbox` exits 1; use Developer → Check Box Content Control (or a legacy FormField checkbox via CLI) |\n| Prompt text (\"Click here to enter a date\") | Needs `placeholderDocPart` in `/word/glossary/document.xml` |\n| Custom richtext default appearance | Adjust the referenced style in Word's style pane |\n| Watermark resize | `width` / `height` not settable; drag shape handles |\n\n(`picture` and `group` SDTs add directly via `--type sdt --prop type=picture` / `type=group` — no longer Phase-2 items.)\n\nFor the first four, build the skeleton once (Path C) and reuse.\n\n## Help pointer\n\nWhen in doubt: `officecli help docx`, `officecli help docx <element>`, `officecli help docx <element> --json`. Help is the authoritative schema; this skill is the decision guide for building real fillable Word forms on top of it.\n","frontmatter":{"name":"officecli-word-form","description":"Use this skill to create fillable Word forms (.docx) with real Content Controls (SDT) + legacy FormField checkboxes + MERGEFIELD mail-merge placeholders + document protection. Trigger on: 'fillable form', 'form fields', 'content controls', 'SDT', 'word form', 'fill in', 'only editable fields', 'protect document', 'onboarding form', 'HR intake', 'survey template', 'contract / SOW template', 'mail-merge template', 'compliance checklist', 'medical intake questionnaire'. Output is a single .docx where specific fields are editable and the rest is locked. This skill is INDEPENDENT, not a scene layer on docx — payload is `w:sdt` + `w:ffData` + `w:fldChar` + `documentProtection`, none of which docx base skill covers. Do NOT trigger for regular reports, letters, memos, academic papers, pitch decks, or any document with no user-fillable fields — route those to officecli-docx or its scene layers."},"isInternal":false,"tokens":12712,"sizeBytes":46994},{"name":"SKILL.md","path":"skills/officecli-xlsx/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli-xlsx/SKILL.md","title":"officecli-xlsx","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli-xlsx\ndescription: \"Use this skill any time a .xlsx file is involved -- as input, output, or both. This includes: creating spreadsheets, financial models, dashboards, or trackers; reading, parsing, or extracting data from any .xlsx file; editing, modifying, or updating existing workbooks; working with formulas, charts, pivot tables, or templates; importing CSV/TSV data into Excel format. Trigger whenever the user mentions 'spreadsheet', 'workbook', 'Excel', 'financial model', 'tracker', 'dashboard', or references a .xlsx/.csv filename.\"\n---\n\n# OfficeCLI XLSX Skill\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## ⚠️ Help-First Rule\n\n**This skill teaches what good xlsx looks like, not every command flag. When a property name, enum value, or alias is uncertain, consult help BEFORE guessing.**\n\n```bash\nofficecli help xlsx                         # List all xlsx elements\nofficecli help xlsx <element>               # Full element schema (e.g. pivottable, chart, cf)\nofficecli help xlsx <verb> <element>        # Verb-scoped (e.g. add chart, set cell)\nofficecli help xlsx <element> --json        # Machine-readable schema\n```\n\nHelp reflects the installed CLI version. When this skill and help disagree, **help is authoritative**.\n\n## Shell & Execution Discipline\n\n**Shell quoting (zsh / bash).** Excel paths contain `[]`, and number formats contain `$`. Both are shell metacharacters. Rules:\n\n- ALWAYS quote element paths: `\"/Sheet1/row[1]\"`, not `/Sheet1/row[1]`.\n- Use **single quotes** for any prop value containing `$`: `numFmt='$#,##0'`.\n- For formulas with cross-sheet `!` references, use `batch` with a `<<'EOF'` heredoc (see Known Issues).\n- `\\n` and `\\t` in a prop value ARE interpreted by the CLI — `\\n` is a real in-cell line break (pair with `--prop wrapText=true`), `\\t` a tab — consistent across xlsx / docx / pptx. Double them (`\\\\n`) for a literal backslash-n (rarely wanted). (`$` is the shell layer above — single-quote it.)\n\n**Incremental execution.** Run commands one at a time and read each exit code. `officecli` mutates the file on every call; a 50-command script that fails at command 3 will cascade silently. One command → check output → continue.\n\n## Requirements for Outputs\n\nBefore reaching for a command, know what a good xlsx looks like. These are the deliverable standards every workbook MUST meet.\n\n### All Excel files\n\n**Zero formula errors.** Every delivered workbook MUST have ZERO `#REF!`, `#DIV/0!`, `#VALUE!`, `#NAME?`, `#N/A`. No exceptions — guard denominators with `IFERROR` or `IF(x=0,...)`.\n\n**Formulas, not hardcoded values.** If a number can be computed from other cells, it is a formula. Hardcoding `5000` where `=SUM(B2:B9)` belongs breaks the contract that the workbook stays live when inputs change. This is the single most important rule in this skill.\n\n**Professional font.** Use one consistent, professional font across the workbook (Arial / Calibri / Times New Roman). Don't mix four fonts because one sheet came from CSV.\n\n**Explicit widths.** There is no auto-fit. Any column the user will read MUST have `width` set — default 8.43 chars clips everything. Sensible starts: labels 20-25, numbers 12-15, dates 12, short codes 8-10.\n\n**Preserve existing templates.** When editing a file that already has a look, match it. Existing conventions override these guidelines.\n\n### Visual delivery floor (applies to EVERY workbook)\n\nBefore you declare done, run `officecli view \"$FILE\" html` and Read the returned HTML path to confirm all of these:\n\n- **No `###` in any cell.** `###` means a column is too narrow for its widest value. Every column the user reads needs an explicit `width`. `###` in a delivered file is unfinished work, never \"a small visual nit\".\n- **No truncated titles.** Sheet titles, section headers, long labels must fit. Widen the column or apply `wrapText=true` on the cell.\n- **No placeholder tokens rendered as data.** `$fy$24`, `{var}`, `<TODO>`, `xxxx` must never appear in a cell, chart title, series name, or legend. These are build-time tokens that escaped replacement.\n- **Pie / doughnut slices have distinct fill colors.** If the slices render same-colored, switch to `bar` / `column` or set `colors=...` explicitly.\n- **No empty trailing pages / empty chart anchors.** `anchor=D2:J18` over empty source cells looks like a broken chart.\n\nIf any of the above fails, STOP and fix before declaring done.\n\n**Print layout.** Any sheet the user may print or send as a board pack needs page setup. Default portrait + no fit-to-page splits wide tables and charts mid-way. Pick the fit mode by sheet shape:\n\n```bash\n# Summary / chart / dashboard sheet (small, ≤ ~40 rows): fit to a single page.\nofficecli set \"$FILE\" \"/Summary\" --prop orientation=landscape --prop fitToPage=true\n# Tall data table (dozens+ rows): fit WIDTH only, let height paginate naturally.\n# fitToPage=true here crushes every row onto one page → unreadable (### dates, 5px rows).\nofficecli set \"$FILE\" \"/Data\" --prop orientation=landscape --prop fitToPage=1x0\n```\n\n`fitToPage=true` == `1x1` == fit both axes to one page — correct only when the sheet is already short. `1x0` = fit 1 page wide, unlimited pages tall. Trigger: sheet holds a chart, or > 8 columns, or the user's ask mentions print / board / investor.\n\n### Financial models only — skip this section if you are building a template, tracker, CSV import, or operational sheet\n\nScope: budgets, forecasts, 3-statement models, valuation, any `$`-heavy analytical workbook. A customer-support tracker or onboarding template does not need this section.\n\n**Color coding — industry standard.** Five core colors used as a language, not decoration. A reviewer should tell what a cell IS by color alone — before reading the formula.\n\n| Color | Role | Example |\n|---|---|---|\n| Blue text `0000FF` | Hardcoded inputs, scenario variables | `font.color=0000FF` |\n| Black text `000000` | ALL formulas and calculations | default |\n| Green text `008000` | Cross-sheet links inside this workbook | `font.color=008000` |\n| Red text `FF0000` | Links to external files / workbooks | `font.color=FF0000` |\n| Yellow fill `FFFF00` | Key assumptions needing review | `fill=FFFF00` |\n\nA reviewer should tell what a cell IS just by its color — before reading the formula. This is a communication contract, not a cosmetic preference.\n\n**Number formatting — standards, not preferences.**\n\n- **Years** are text, not numbers. Format `2026` not `2,026` — use `numFmt=\"@\"` or set `type=string`.\n- **Currency** carries its unit in the header (`Revenue ($mm)`), not in every cell.\n- **Zeros display as `-`**, not `0`. Use `$#,##0;($#,##0);\"-\"`.\n- **Percentages** default to one decimal: `0.0%`.\n- **Negatives use parentheses**: `(1,234)` not `-1,234`.\n- **Valuation multiples** use `0.0x` format (EV/EBITDA, P/E, etc.).\n\n**Assumptions live in cells, not inside formulas.** `=B5*(1+$B$6)` is correct; `=B5*1.05` is a bug. Document each blue hardcoded input with an adjacent source note in the next cell or a cell comment:\n\n```\nSource: Company 10-K, FY2024, Page 45, Revenue Note\nSource: Bloomberg, 2026-05-02, AAPL US Equity\nSource: Management guidance, Q2 2026 earnings call\n```\n\nAny hardcoded number without a source is an undocumented assumption — a reviewer cannot audit it.\n\n## Common Workflow\n\nSix steps. Every non-trivial build follows this shape.\n\n1. **Open/save lifecycle.** Use `officecli open <file>` at the start and `officecli save <file>` at the end to flush to disk — `save` only writes and leaves the resident warm for follow-up edits; reach for `officecli close <file>` only to release the resident on a one-shot handoff. Both are always safe (never error or lose work). For many cells, use `batch`: **≤ 50 ops/block recommended; tested up to 80+ ops per block on pure value-set payloads with zero failures. Cross-sheet formula batches are the exception — run those non-resident, single heredoc (see Known Issues)**. **Flush only at the non-officecli boundary:** officecli's own reads always see your edits; run `save`/`close` only before a non-officecli program reads the file (openpyxl/pandas, Excel, a renderer, delivery).\n2. **Create or load.** `officecli create \"$FILE\"` (new) or `officecli view \"$FILE\" outline` (existing — get the lay of the land first).\n3. **Build incrementally.** One command, read the output, continue. After any structural op (new sheet, chart, named range, pivot), run `get` on it to confirm shape before stacking more on top.\n4. **Format.** Column widths, number formats, freeze panes, tab colors, header fills. Formatting is not optional polish — per \"Requirements for Outputs\" it is part of the deliverable.\n5. **Save, then reckon with the cache.** `officecli save <file>` writes to disk. Newly-added formulas ship without cached values; when a human opens the file in a spreadsheet app, the app recalculates and populates them. **But your downstream `INDEX/MATCH`, `SUMPRODUCT`, or any formula that references an upstream formula will cache whatever the upstream cached at write-time — often `0` or a stale value — and that cached lie survives into non-recalculating readers.** After any multi-formula build involving array formulas (`SUMPRODUCT`, `SUMIFS` with dynamic criteria) or cross-sheet chains, **re-touch every downstream cell** (run `set` again with the same formula) so the engine recomputes its cache from the freshly-cached upstream. ⚠️ Re-touch on cross-sheet chains via resident is unreliable (see Batch / resident caveats) — prefer non-resident `set` for the re-touch pass. Then `officecli get` a few downstream cells and eyeball that their `cachedValue=` is plausible. `validate` is safe with a resident open and itself flushes pending edits to disk (same as docx / pptx).\n6. **QA — assume there are problems.** See the QA section. You are not done when your last command exited 0; you are done after one fix-and-verify cycle finds zero new issues.\n\n## Quick Start\n\nMinimal viable xlsx: 3 months of revenue + a total formula + column widths + a currency format. Adapt, don't copy-paste — your file, your data.\n\n```bash\nofficecli create \"$FILE\"\nofficecli open \"$FILE\"\nofficecli set \"$FILE\" /Sheet1/A1 --prop value=Month --prop bold=true\nofficecli set \"$FILE\" /Sheet1/B1 --prop value=Revenue --prop bold=true\nofficecli set \"$FILE\" /Sheet1/A2 --prop value=Jan\nofficecli set \"$FILE\" /Sheet1/A3 --prop value=Feb\nofficecli set \"$FILE\" /Sheet1/A4 --prop value=Mar\nofficecli set \"$FILE\" /Sheet1/B2 --prop value=42000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/B3 --prop value=45000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/B4 --prop value=48000 --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/A5 --prop value=Total --prop bold=true\nofficecli set \"$FILE\" /Sheet1/B5 --prop formula=\"SUM(B2:B4)\" --prop bold=true --prop numFmt='$#,##0'\nofficecli set \"$FILE\" \"/Sheet1/col[A]\" --prop width=12\nofficecli set \"$FILE\" \"/Sheet1/col[B]\" --prop width=15\nofficecli close \"$FILE\"\nofficecli validate \"$FILE\"\n```\n\nVerified: `validate` returns `no errors found`, `B5` resolves to `135000`. This is the shape of every build: open → set cells/formulas → format → close → validate.\n\n## CSV / bulk import\n\n**Native `import` command (preferred for CSV/TSV).** Fastest path; loads a CSV into a sheet in one call. `--header` sets AutoFilter + freeze pane on row 1. Widths and `numFmt` still need a follow-up pass (per D-12 in Dashboard skill).\n\n```bash\nofficecli import \"$FILE\" /Sheet1 --file data.csv --header\nofficecli import \"$FILE\" /Sheet1 --file data.tsv --format tsv --header\nofficecli import \"$FILE\" /Sheet1 --stdin --start-cell B2 < data.csv\n```\n\n**Python + batch fallback** — use when you need custom type coercion, formula injection, or the CSV lives inside another data pipeline. Recipe for 600-6000+ cells:\n\n```python\n# gen_batch.py — produces batch chunks of 80 value-set ops each\nimport csv, json\nops = []\nwith open(\"data.csv\") as f:\n    reader = csv.reader(f)\n    for r, row in enumerate(reader, start=1):\n        for c, val in enumerate(row):\n            col = chr(ord('A') + c)\n            ops.append({\"command\":\"set\",\"path\":f\"/Data/{col}{r}\",\n                        \"props\":{\"value\": val}})\nfor i in range(0, len(ops), 80):\n    print(json.dumps(ops[i:i+80]))\n```\n\n```bash\npython gen_batch.py | while IFS= read -r chunk; do\n  printf '%s\\n' \"$chunk\" | officecli batch \"$FILE\"\ndone\n```\n\nOutcome: 648-row retail CSV (6490 cells) loads in ~30s, zero failures. Tune: start at 80 ops/chunk, drop to 40 if any chunk fails. Numeric type inference and formulas come later via targeted `set` — batch in this recipe is pure value injection.\n\n## Reading & Analysis\n\nStart wide, then narrow. `outline` first tells you what sheets exist and where the data is; jump into `view` / `get` / `query` only once you know where to look.\n\n**Open the rendered workbook to eyeball your own work.**\n- `officecli view $FILE html` — Read the returned HTML to audit the rendered output. Each sheet is addressable, charts render inline. Catches `###`, placeholder leakage, pivot layout, row-height clipping.\n- `officecli watch $FILE` keeps a live preview running for the human user — they open it at their own discretion. Use when the user wants to watch along; agent self-check uses `view html` above.\nUse `view html` as your **first visual check after a batch of edits** — fix at source. For final visual verification, the user opens the `.xlsx` in their Excel / WPS / Numbers viewer.\n\n**Orient.** Sheets, dimensions, formula counts.\n\n```bash\nofficecli view \"$FILE\" outline\n```\n\n**Extract.** Plain text dump for content QA or LLM context; scope with `--start` / `--end` / `--cols` for big files.\n\n```bash\nofficecli view \"$FILE\" text --start 1 --end 50 --cols A,B,C\n```\n\nOther `view` modes worth knowing: `annotated` (cell values + types/formulas + warnings), `stats` (numeric summaries), `issues` (broken formulas, empty sheets, missing refs).\n\n**Round-trip dump.** `officecli dump \"$FILE\" [path]` serializes the workbook — or one worksheet (`/Sheet1`, `/sheet[N]`) — into a replayable batch JSON; `officecli batch new.xlsx --input dump.json` replays it. Use it to learn from an existing workbook's structure or clone/adapt a template instead of reading raw OOXML. Coverage per `dump --help`; subtree dumps don't carry workbook-level resources (settings, named ranges) — the replay target must already define them.\n\n```bash\nofficecli dump \"$FILE\" -o blueprint.json            # whole workbook\nofficecli dump \"$FILE\" /Sheet1 -o sheet.json        # one worksheet\nofficecli batch new.xlsx --input blueprint.json\n```\n\n**Inspect one element.** Use XPath-style paths. Always quote — shells glob `[N]`.\n\n```bash\nofficecli get \"$FILE\" \"/Sheet1/A1\"            # one cell\nofficecli get \"$FILE\" \"/Sheet1/A1:D10\"        # range\nofficecli get \"$FILE\" \"/Sheet1/chart[1]\"      # chart\nofficecli get \"$FILE\" \"/Sheet1/table[1]\"      # ListObject\nofficecli get \"$FILE\" \"/namedrange[1]\"        # workbook-level named range\n```\n\nAdd `--depth N` to expand children; add `--json` for machine output. Full element list: `officecli help xlsx`.\n\n**Query across the workbook.** CSS-like selectors. Use for systematic checks (formula coverage, error cells, empty headers) rather than hand-walking.\n\n```bash\nofficecli query \"$FILE\" 'cell:has(formula)'       # every formula cell\nofficecli query \"$FILE\" 'cell:contains(\"#REF!\")'  # broken references\nofficecli query \"$FILE\" 'cell[type=Number]'       # typed filter\nofficecli query \"$FILE\" 'Sheet1!B[value!=0]'      # sheet-scoped\n```\n\nOperators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists).\n\n**Merge cells shortcut.** `officecli query $FILE merge` or `mergedrange` — both are aliases for `mergeCell`. Returns every merged range in the workbook without hand-walking `<mergeCell>` entries.\n\n**When the data is big enough that a row-walk is useless**, reach for Excel's own analytical elements:\n\n- Build a **pivot table** with `officecli add` (`--type pivottable`) to group/aggregate without writing 20 SUMIFs. Attach a **slicer** (`--type slicer`) to give the reader a filter UI.\n- Drop a **sparkline** (`--type sparkline`) in a row to show per-row trends — cheaper than one line chart per row and they print inline. `type` is a strict enum: **`line | column | stacked`** (plus aliases `winloss` / `win-loss` → `stacked`). Invalid `type=` values hard-fail — no silent fallback to `line` anymore.\n- Run `officecli help xlsx pivottable`, `officecli help xlsx slicer`, `officecli help xlsx sparkline` for the exact prop names.\n\n## Creating & Editing\n\nNinety percent of a build is cells, formulas, formatting, and one or two charts. The verbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`.\n\n### Cells and formulas\n\nSet a value and its format in one call. Never write `=` at the start of a formula — the CLI strips it.\n\n```bash\nofficecli set \"$FILE\" /Sheet1/B5 --prop formula=\"SUM(B2:B4)\" --prop numFmt='$#,##0'\nofficecli set \"$FILE\" /Sheet1/C5 --prop formula=\"B5/A5\" --prop numFmt=\"0.0%\"\n```\n\nStructural properties (width, height, freeze, tabColor) live on row / col / sheet nodes:\n\n```bash\nofficecli set \"$FILE\" \"/Sheet1/col[A]\" --prop width=20\nofficecli set \"$FILE\" \"/Sheet1/row[1]\" --prop height=22\nofficecli set \"$FILE\" \"/Sheet1\" --prop freeze=A2 --prop tabColor=1F4E79\n```\n\n### Named ranges\n\nPrefer named ranges over `$B$6` in formulas. They self-document (`GrowthRate` beats `$B$6`) and they let you move the assumption cell without breaking formulas. Because `ref` values contain both `!` and `$`, add them through a batch heredoc:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"GrowthRate\",\"ref\":\"Sheet1!$B$6\"}}\n]\nEOF\n```\n\nSee `officecli help xlsx namedrange` for the full schema.\n\n**Batch JSON does NOT accept shell aliases.** Inside batch `props`, always use the full dotted name — `\"font.color\": \"FF0000\"`, `\"font.size\": 14`, never `\"color\": \"FF0000\"` (ambiguous: text vs fill). On a bare cell, even the shell form is rejected: `--prop color=1F4E79` errors with `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. Rule: in any batch JSON or cell prop, write `font.color` / `fill` explicitly. `parent` should be `\"/\"` for workbook-level elements and `\"/SheetName\"` for sheet-scoped; empty string is not equivalent.\n\n### Charts\n\nChart types live under `officecli help xlsx chart` — the enum is long (20+). Pick the right one for the message: column for category comparison, line for time series, pie only when slices are self-evidently proportional, scatter for correlation. Avoid exotic types unless they answer a specific question.\n\n**Three ways to feed chart data. Pick one per chart — mixing them at add-time is a common trap.**\n\n| Form | Shape | When to use |\n|---|---|---|\n| (a) inline `data` | `--prop data=\"Sales:100,200,300\" --prop categories=\"Jan,Feb,Mar\"` | Tiny demo charts, numbers you will not edit. Source of truth lives in the chart XML, not a cell. |\n| (b) 2D `dataRange` | `--prop dataRange=\"Sheet1!A1:B4\"` (first col = categories, first row = header / series name) | Normal case. Must be **2-D** — single column fails with \"Chart requires data\". |\n| (c) dotted per-series | `--prop series1.name=Sales --prop series1.values=\"Sheet1!B2:B4\" --prop series1.categories=\"Sheet1!A2:A4\"` | Multi-series charts where each series points at non-contiguous ranges, or you want explicit series naming. `series1.values` alone (no `categories`) emits a chart with `1,2,3` as the x-axis. |\n\n**The single-column trap.** `dataRange=\"Sheet1!B2:B13\"` looks like \"value column\" but the engine rejects it with `Chart requires data`. Either widen the range to include the category column (`A2:B13`), or switch to form (c) with explicit `series1.categories`.\n\n**Move / resize a chart after create:** `set chart[N] --prop anchor=\"F5:N25\"` (also `--prop x= --prop y= --prop width= --prop height=`). **Series are still immutable** — to add/change a series, `officecli remove` the chart and `officecli add` with the full series list. Note `remove chart[1]` shifts `chart[2] → chart[1]` and re-add **appends at the end** — to preserve chart order, remove all and rebuild in order.\n\n**Anchor sizing.** No auto-fit. A column chart with 5-6 categories + 2 series needs roughly `A5:L22` (12 cols × 18 rows) to show all labels uncut. Narrower and X-axis labels clip; wider and the chart can split across pages on print/export. If in doubt, start narrow, preview via `view html` (Read the returned HTML path), widen in increments. Page layout (below) is the other half of the fix.\n\n**Chart `dataRange` — always prefix with the sheet.** Even when the chart lives on the same sheet, write `dataRange=\"Summary!A17:C22\"`, not `A17:C22`. The sheet-less form works inconsistently; the prefixed form is 100% reliable.\n\nofficecli adds extended chart types the classic Excel object model lacks: `boxWhisker`, `waterfall`, `funnel`, `histogram`, `treemap`, `sunburst`, `pareto`. Use them when the data calls for them.\n\n**NEVER put unreplaced template tokens in chart title / series name / legend / axis title.** `$fy$24`, `{var}`, `<TODO>`, `$VAR`, `{{placeholder}}` render **literally** in the legend — validate passes, but a CFO sees `$fy$24` where \"FY2024\" should be. Always bind to final text or a cell reference (`title=\"FY2024 Revenue\"` or `series1.name=\"Sheet1!A1\"`).\n\n### Conditional formatting\n\nThree common flavors, each with its own prop shape (consult `officecli help xlsx cf`):\n\n- **Color scales**: cells shaded on a gradient by value — `type=colorscale` with `minColor` / `midColor` / `maxColor`.\n- **Data bars**: in-cell bars showing magnitude — `type=databar`. Set explicit `min` / `max` for consistent scaling across a column; defaults are valid if you omit them.\n- **Formula rules** (the `formulacf` element): highlight row when a condition is true — `type=formula` with `formula=\"$C2>1000\"` and a fill/font.\n\nRule: apply CF sparingly. A workbook where every cell is colored tells the reader nothing.\n\n### Data validation\n\nInput cells in trackers and templates MUST carry data validation. It's cheap and it stops entire classes of downstream bugs. **Three list-source patterns** — pick based on where the allowed values live.\n\n**(a) Inline list** — allowed values are short and fixed in the rule itself.\n\n```bash\nofficecli add \"$FILE\" /Sheet1 --type validation \\\n  --prop sqref=\"C2:C100\" --prop type=list \\\n  --prop formula1=\"Yes,No,Maybe\" \\\n  --prop showError=true --prop errorTitle=\"Invalid\" --prop error=\"Select from list\"\n```\n\n**(b) Named range (preferred for cross-sheet lookups)** — allowed values live in another sheet and may grow. Define the named range first, then reference it. Use a batch heredoc because `ref` contains `!` and `$`:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/\",\"type\":\"namedrange\",\"props\":{\"name\":\"StatusList\",\"ref\":\"Lookups!$A$2:$A$4\"}},\n  {\"command\":\"add\",\"parent\":\"/Sheet1\",\"type\":\"validation\",\"props\":{\"sqref\":\"B2:B100\",\"type\":\"list\",\"formula1\":\"=StatusList\"}}\n]\nEOF\n```\n\n**(c) Direct cross-sheet range** — no named range, raw `Lookups!$A$2:$A$4` inside `formula1`. Also needs a batch heredoc to keep `!` and `$` intact:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[\n  {\"command\":\"add\",\"parent\":\"/Sheet1\",\"type\":\"validation\",\"props\":{\"sqref\":\"C2:C100\",\"type\":\"list\",\"formula1\":\"Lookups!$A$2:$A$4\"}}\n]\nEOF\n```\n\nIf you write the cross-sheet variant as `--prop formula1=...` on the shell, the `!` gets shell-mangled into `\\!` and the dropdown will silently fall back to no list. Verify with `officecli get \"$FILE\" /Sheet1/validation[N]` — `formula1=` must show a plain `!`, no backslash.\n\nOther common `type` values: `decimal`, `whole`, `date`, `textLength`, `custom`. See `officecli help xlsx validation` for operators and the full prop list.\n\n### Other elements (one-liners)\n\n- **Tables** (ListObjects) — `add --type table` with a range; gives auto-filter + structured refs. `officecli help xlsx table`.\n- **Comments** — `add --type comment`; use for documenting hardcoded assumptions. `officecli help xlsx comment`.\n- **Sheet reordering** — `officecli move`, not `swap`. `swap` only works on row/cell paths.\n- **New row/column with the neighbours' formatting** — `add \"$FILE\" /Sheet1 --from \"/Sheet1/row[5]\" --index 5` clones fills, fonts and formulas (refs shift); `add --type row` inserts a bare row.\n\n## Chart Axis-by-Role\n\nEditing a chart axis in place is cheaper than rebuilding the chart. Address axes by **role** (`value` = Y, `category` = X), not by index — the XML order isn't stable.\n\n```bash\nofficecli get \"$FILE\" \"/Sheet1/chart[1]/axis[@role=value]\"\nofficecli set \"$FILE\" \"/Sheet1/chart[1]/axis[@role=value]\" --prop min=0 --prop max=100000\nofficecli set \"$FILE\" \"/Sheet1/chart[1]/axis[@role=category]\" --prop title=\"Month\"\n```\n\nSafe props: `title`, `min`, `max`, `majorGridlines`, `visible`, `labelRotation`.\n\n## QA (Required)\n\n**Assume there are problems. Your job is to find them.**\n\nYour first workbook is almost never correct. Treat QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you were not looking hard enough. The formulas look fine **until** you check two of them against source cells.\n\n### Minimum cycle before \"done\"\n\n1. `officecli view \"$FILE\" issues` — empty sheets, broken formulas, missing refs.\n2. `officecli view \"$FILE\" annotated` (sample ranges) — values + types + warnings.\n3. For every Excel error type, query it:\n   ```bash\n   officecli query \"$FILE\" 'cell:contains(\"#REF!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#DIV/0!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#VALUE!\")'\n   officecli query \"$FILE\" 'cell:contains(\"#NAME?\")'\n   officecli query \"$FILE\" 'cell:contains(\"#N/A\")'\n   ```\n4. `officecli validate \"$FILE\"` — safe with a resident open; `validate` flushes pending edits to disk itself.\n5. **Visual pass — walk every sheet via the HTML preview.** Run `officecli view \"$FILE\" html` and Read the returned HTML path. Each sheet renders with charts inline. Scan for `###`, truncated titles, placeholder tokens (`$fy$24`, `{var}`, `<TODO>`), sliced charts, white-slice pie charts, empty chart anchors — **STOP and fix before declaring done**. \"validate pass\" is not delivery; \"the preview looks like a real workbook\" is delivery. For human preview, run `officecli watch \"$FILE\"` (user opens the live preview at their own discretion) or have them open the `.xlsx` directly in Excel / WPS / Numbers.\n6. **Print layout fix (wide tables / multi-chart sheets).** When a sheet holds a chart or a wide table and the user will print it, set per-sheet page layout — but match the fit mode to the sheet's height:\n   ```bash\n   # Short summary / chart sheet → fit to one page.\n   officecli set \"$FILE\" \"/Summary\" --prop orientation=landscape --prop fitToPage=true\n   # Tall data table → fit width only (fitToPage=true would crush all rows onto one unreadable page).\n   officecli set \"$FILE\" \"/Data\" --prop orientation=landscape --prop fitToPage=1x0\n   ```\n   Outcome: charts/wide tables print without mid-chart splits; tall tables stay readable across natural page breaks. Apply to every sheet that holds a chart or a > 8-column table.\n7. If anything failed, fix, then **rerun the full cycle**. One fix commonly creates another problem.\n\n`officecli view issues` + `view html` are the structural QA pair: `issues` catches broken formulas and empty sheets; `view html` (Read the returned HTML path) catches `###`, truncation, and token leakage. Chart fill colors / theme tints can vary across viewers — spot-check in the user's target viewer when color fidelity matters.\n\n### Formula verification checklist\n\n- [ ] Pick 2-3 formulas at random. Run `officecli get` on each. Confirm the formula string is what you intended **and** `cachedValue=` is what you expect — arithmetic in your head.\n- [ ] **Cached value sanity on every summary cell.** Any cell that aggregates (COUNTA / COUNTIF / SUMPRODUCT / INDEX&MATCH) must have a plausible `cachedValue`. If a progress tracker shows `199 / 199 / 100%` on a blank template, the cache is lying — re-touch the formula via `set` (forces recompute) or manually set a correct cached value. Do NOT ship \"validate passes but the numbers are fiction\".\n- [ ] **Spot-check one cell per numeric column.** `%` columns showing integer `0.0%` throughout means the denominator is wrong or the numerator is cached stale — investigate one cell, fix the pattern.\n- [ ] Ranges include every row: off-by-one on `SUM(B2:B12)` when data goes to `B13` is the most common bug.\n- [ ] Cross-sheet formulas (`Sheet1!A1`) contain no `\\!`. If `officecli get` shows `Sheet1\\!A1`, the `!` was shell-corrupted — delete and re-enter via batch/heredoc.\n- [ ] Named ranges (`officecli get \"$FILE\" \"/namedrange[1]\"`) point at what their names claim.\n- [ ] Every `/` denominator is guarded — `IFERROR(x/y, 0)` or `IF(y=0, 0, x/y)`.\n- [ ] Chart data vs source cells: for every chart with inline data, spot-check data points against `officecli get` of the source cells.\n- [ ] Chart title / series name / legend contain **no** unreplaced tokens (`$...$`, `{var}`, `<TODO>`). Grep the chart via `officecli get /Sheet1/chart[N]`.\n\n### Template QA\n\nWhen editing a template, check for leftover placeholders — they look like content and slip past `validate`:\n\n```bash\nofficecli query \"$FILE\" 'cell:contains(\"{{\")'\nofficecli query \"$FILE\" 'cell:contains(\"xxxx\")'\nofficecli query \"$FILE\" 'cell:contains(\"TBD\")'\n```\n\n### Fresh eyes\n\nWhen you finish a workbook, open it fresh. Read `view text` / HTML preview top-to-bottom as if you are a new reviewer — look for formulas, numbers that look off, formatting inconsistency, missing data.\n\n### Honest limit\n\n`validate` catches schema errors, not design errors. A workbook can pass `validate` with every number wrong. The checklist above — especially spot-checking formulas against source cells — is how you catch what validation can't.\n\n## Known Issues & Pitfalls\n\n### The cross-sheet `!` trap (short)\n\nShells (bash history expansion, zsh splitting) and CLI arg parsing mangle `!` in `Sheet1!A1` into `\\!`. A formula containing `\\!` is silently broken — it renders as literal text and references nothing.\n\n**Fix.** Use a batch heredoc with single-quoted delimiter (`<<'EOF'`), which disables all shell expansion:\n\n```bash\ncat <<'EOF' | officecli batch \"$FILE\"\n[{\"command\":\"set\",\"path\":\"/Summary/B2\",\"props\":{\"formula\":\"Revenue!B13\"}}]\nEOF\n```\n\n**Verify.** After writing, `officecli get` the cell; `formula=` must show a plain `!` with no backslash.\n\n### CLI bug backlog (short)\n\nCLI constraints and gaps to work around — not defects in the output file.\n\n- **Chart series are immutable after create** — to add/change a series: `remove` + `add` with the full series list. (Position is mutable: `set chart[N] --prop anchor=` / `x/y/width/height`.) `remove chart[N]` shifts subsequent indices down; re-add appends at end.\n- **Cross-sheet formula batches run fine through a resident** — a prior \"deadlocks even at 3-5 ops\" caution no longer reproduces. Pure value-set batches stay reliable at 50-80+ ops too. If you ever hit a hang, fall back to a non-resident one-big-batch or individual `set`. **Multiple resident processes on the same file/machine can still contend** — expect non-deterministic hangs if another agent/session holds a resident on the same file.\n- **Conditional formatting naming asymmetry** — the element name for `--type` is `conditionalformatting`; the path suffix is `/cf[N]`. Use `officecli help xlsx conditionalformatting` for schema, `/cf[N]` for paths.\n- **Sheet `position` prop on add** — help says Add processes `position`, but the prop is often ignored. Reorder with `officecli move --index` / `--after` / `--before` after creating the sheet.\n- **`remove /sheet[N]` cascade guard** — rejects sheet remove/rename when the sheet is referenced by validation / conditional format / sparkline / hyperlink / named range on another sheet. Remove those dependent elements first, then remove the sheet.\n- **Batch JSON rejects cell `color` alias** — inside batch `props`, `\"color\": \"FF0000\"` errors `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. The CLI at shell level accepts `--prop color=...` / `--prop size=14` as aliases on non-cell elements, but inside batch JSON on a cell always write the full dotted name: `\"font.color\"`, `\"font.size\"`, `\"font.name\"`.\n\n### Renderer caveats (cross-viewer color fidelity)\n\n`officecli view html` is the right tool for structural QA (overflow, truncation, placeholder leakage, layout) — Read the returned HTML path. Some chart rendering details vary across the viewer the end user opens the file in. Observed divergences:\n\n- **Pie / doughnut fill colors may collapse to a single theme tint** in some viewers (slices look \"all white\" or \"all one color\"). The file may be fine in the user's target viewer.\n- **Line chart / column chart series colors may drift** from the workbook theme in some viewers.\n- **Form-control checkboxes may render as double-boxed** in some viewers.\n\nBefore calling a color or chart \"broken\", open the file in the user's actual target viewer. If it looks correct there, the problem is viewer rendering, not data — do not chase it. The CLI's structural checks (`###`, truncation, placeholder text, layout) remain authoritative.\n\n### Escape layers (shell quoting is above; these are the extras)\n\n`$` is the shell layer (single-quote it, above). `\\n` / `\\t` in a prop value ARE interpreted by the CLI into a real newline / tab. Two more layers:\n\n- **JSON level (batch).** Standard JSON escapes — `\"\\n\"`, `\"\\t\"`, `\"\\\"\"`. A real backslash in the final string is `\"\\\\\\\\\"`.\n- **Excel level.** `\\n` in a cell is a real line break — pair with `--prop wrapText=true` so Excel shows the wrap. Works in a shell-quoted prop directly (`--prop value='a\\nb'`); `\"\\n\"` inside batch JSON gives the same. When in doubt, `officecli get` the cell and compare character-for-character.\n\n### Other common pitfalls\n\n| Pitfall | Fix |\n|---|---|\n| `--name \"foo\"` | All attrs go through `--prop`: `--prop name=\"foo\"` |\n| Guessing a prop name | `officecli help xlsx <element>` — don't improvise |\n| `--prop color=...` on a cell | Ambiguous — use `font.color` (text) or `fill` (bg). Also applies inside batch JSON: always use full dotted names, never shell aliases |\n| `#FF0000` hex colors | Drop the `#`: `FF0000` |\n| `--index` vs `[N]` | `--index` is 0-based (array); `[N]` paths are 1-based (XPath) |\n| Unquoted `[N]` in zsh/bash | Quote every path: `\"/Sheet1/row[1]\"` |\n| Sheet name with spaces | Quote full path: `\"/My Sheet/A1\"` |\n| Year showing as `2,026` | `--prop type=string` or `numFmt=\"@\"` |\n| Modifying a file open in Excel | Close it in Excel first |\n| `swap` not reordering sheets | `swap` is for rows/cells. Use `move --after` / `--before` / `--index` for sheets |\n| Cached values missing after write | New formulas get cached values when a human opens the file; `validate` accepts them either way |\n","frontmatter":{"name":"officecli-xlsx","description":"Use this skill any time a .xlsx file is involved -- as input, output, or both. This includes: creating spreadsheets, financial models, dashboards, or trackers; reading, parsing, or extracting data from any .xlsx file; editing, modifying, or updating existing workbooks; working with formulas, charts, pivot tables, or templates; importing CSV/TSV data into Excel format. Trigger whenever the user mentions 'spreadsheet', 'workbook', 'Excel', 'financial model', 'tracker', 'dashboard', or references a .xlsx/.csv filename."},"isInternal":false,"tokens":9214,"sizeBytes":35502},{"name":"SKILL.md","path":"skills/officecli/SKILL.md","rawUrl":"https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/officecli/SKILL.md","title":"officecli","category":"anthropic-skill","format":"markdown","content":"---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n","frontmatter":{"name":"officecli","description":"Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents."},"isInternal":false,"tokens":6957,"sizeBytes":25974}],"systemPromptSnippet":"<yakaai_skills repo=\"iOfficeAI/OfficeCLI\">\n<!-- File: plugins/plugin-protocol.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `plugins/plugin-protocol.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/plugins/plugin-protocol.md)\n\n<!-- File: SKILL.md (Tokens: ~6957 | Category: anthropic-skill) -->\n---\nname: officecli\ndescription: Create, analyze, proofread, and modify Office documents (.docx, .xlsx, .pptx) using the officecli CLI tool. Use when the user wants to create, inspect, check formatting, find issues, add charts, or modify Office documents.\n---\n\n# officecli\n\nAI-friendly CLI for .docx, .xlsx, .pptx. Single binary, no dependencies, no Office installation needed.\n\n## Install\n\nIf `officecli` is not installed:\n\n```bash\n# macOS / Linux\ncurl -fsSL https://d.officecli.ai/install.sh | bash\n\n# Windows (PowerShell)\nirm https://d.officecli.ai/install.ps1 | iex\n```\n\nVerify with `officecli --version`. If still not found after install, open a new terminal.\n\n---\n\n## Strategy\n\n**L1 (read) → L2 (DOM edit) → L3 (raw XML)**. Always prefer higher layers. Add `--json` for structured output.\n\n**Before doc work, check Specialized Skills** (bottom of this file). Fundraising decks, academic papers, financial models, dashboards, and Morph animations need their own skill loaded first — `load_skill` once, then proceed.\n\n---\n\n## Help System (IMPORTANT)\n\n**When unsure about property names, value formats, or command syntax, ALWAYS run help instead of guessing.** One help query beats guess-fail-retry loops.\n\n`officecli help` ≡ `officecli --help`, and `officecli <cmd> --help` ≡ `officecli help <cmd>` — same content.\n\n```bash\nofficecli help                                  # All commands + global options + schema entry points\nofficecli help docx                             # List all docx elements\nofficecli help docx paragraph                   # Full schema: properties, aliases, examples, readbacks\nofficecli help docx set paragraph               # Verb-filtered: only props usable with `set`\nofficecli help docx paragraph --json            # Structured schema (machine-readable)\n```\n\nFormat aliases: `word`→`docx`, `excel`→`xlsx`, `ppt`/`powerpoint`→`pptx`. Verbs: `add`, `set`, `get`, `query`, `remove`. MCP exposes the same schema via the single `command` string param: `{\"command\":\"help docx paragraph\"}` (not a structured `{\"format\":...,\"type\":...}` object — the MCP tool has exactly one param, `command`, and passes it through to the CLI verbatim).\n\n---\n\n## Performance: Resident Mode\n\n**Every command auto-starts a resident on first access** (60s idle timeout) — file-lock conflicts are automatically avoided. Explicit `open`/`close` is still recommended for longer sessions (12min idle):\n```bash\nofficecli open report.docx       # explicitly keep in memory\nofficecli set report.docx ...    # no file I/O overhead\nofficecli close report.docx      # save and release\n```\n\nOpt out of auto-start: `OFFICECLI_NO_AUTO_RESIDENT=1`.\n\n**Flush only at the non-officecli boundary.** officecli's own reads (`get`/`query`/`view`/`dump`) always see your latest edits, so you never need to save mid-workflow. Run `save` (keeps the resident) or `close` (flush + release) only **before a non-officecli program reads the file** — python-docx/openpyxl, Word, a renderer, delivery/upload. (Idle sessions auto-flush within seconds; `OFFICECLI_RESIDENT_FLUSH=each` makes every mutation flush before returning.)\n\n---\n\n## Quick Start\n\n**PPT:**\n```bash\nofficecli create slides.pptx\nofficecli add slides.pptx / --type slide --prop title=\"Q4 Report\" --prop background=1A1A2E\nofficecli add slides.pptx '/slide[1]' --type shape --prop text=\"Revenue grew 25%\" --prop x=2cm --prop y=5cm --prop font=Arial --prop size=24 --prop color=FFFFFF\n```\n\n**Word:**\n```bash\nofficecli create report.docx\nofficecli add report.docx /body --type paragraph --prop text=\"Executive Summary\" --prop style=Heading1\nofficecli add report.docx /body --type paragraph --prop text=\"Revenue increased by 25% year-over-year.\"\n```\n\n**Excel:**\n```bash\nofficecli create data.xlsx\nofficecli set data.xlsx /Sheet1/A1 --prop value=\"Name\" --prop bold=true\nofficecli set data.xlsx /Sheet1/A2 --prop value=\"Alice\"\n```\n\n---\n\n## L1: Create, Read & Inspect\n\n```bash\nofficecli create <file>               # Create blank .docx/.xlsx/.pptx (type from extension)\nofficecli view <file> <mode>          # outline | stats | issues | text | annotated | html\nofficecli get <file> <path> --depth N # Get a node and its children [--json]\nofficecli query <file> <selector>     # CSS-like query\nofficecli validate <file>             # Validate against OpenXML schema\n```\n\n### view modes\n\n| Mode | Description | Useful flags |\n|------|-------------|-------------|\n| `outline` | Document structure | |\n| `stats` | Statistics (pages, words, shapes) | |\n| `issues` | Formatting/content/structure problems | `--type format\\|content\\|structure`, `--limit N` |\n| `text` | Plain text extraction | `--start N --end N`, `--max-lines N` |\n| `annotated` | Text with formatting annotations | |\n| `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) |\n| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` |\n\nUse `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select.\n\n### get\n\nAny XML path via element localName. Use `--depth N` to expand children. Add `--json` for structured output. Default text output is grep-friendly: `path (type) \"text\" key=val key=val ...`\n\n```bash\nofficecli get report.docx '/body/p[3]' --depth 2 --json\nofficecli get slides.pptx '/slide[1]' --depth 1          # list all shapes on slide 1\nofficecli get data.xlsx '/Sheet1/B2' --json\n```\n\n### Stable ID Addressing\n\nElements with stable IDs return `@attr=value` paths instead of positional indices. Prefer these in multi-step workflows — positional indices shift on insert/delete, stable IDs do not.\n\n```\n/slide[1]/shape[@id=550950021]                    # PPT shape\n/slide[1]/table[@id=1388430425]/tr[1]/tc[2]       # PPT table\n/body/p[@paraId=1A2B3C4D]                         # Word paragraph\n/comments/comment[@commentId=1]                    # Word comment\n```\n\nPPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix awareness. Elements without stable IDs (slide, run, tr/tc, row) fall back to positional indices.\n\n### query\n\nCSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains(\"text\")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`.\n\n```bash\nofficecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]'\nofficecli query slides.pptx 'shape[fill=FF0000]'\n```\n\n---\n\n## Watch & Interactive Selection\n\nLive HTML preview that auto-refreshes on every file change. Browsers can click / shift-click / box-drag to select shapes; the CLI can read the current browser selection and act on it.\n\n```bash\nofficecli watch <file> [--port N]      # Start preview server (default port 26315)\nofficecli unwatch <file>               # Stop\nofficecli goto <file> <path>           # Scroll watching browser(s) to element (docx: p / table / tr / tc)\n```\n\nOpen the printed `http://localhost:N` URL. Click to select; shift/cmd/ctrl+click to multi-select; drag from empty space to box-select. PPT/Word use blue outline; Excel uses native-style green selection (double-click cell to edit inline; drag a chart to reposition).\n\n### `get <file> selected` — read what the user clicked\n\n```bash\nofficecli get <file> selected [--json]\n```\n\nReturns DocumentNodes for whatever is currently selected. Empty result if nothing selected. Exit code != 0 if no watch is running.\n\n```bash\n# User clicks shapes in the browser, then asks \"make these red\"\nPATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')\nfor p in $PATHS; do officecli set deck.pptx \"$p\" --prop fill=FF0000; done\n```\n\n### Key properties\n\n- **Selection survives file edits.** Paths use stable `@id=` form.\n- **All connected browsers share one selection.** Last-write-wins.\n- **Same-file single-watch.** A given file can have only one watch process at a time.\n- **Group shapes select as a whole.** Drilling into individual children of a group is not supported in v1.\n- **Coverage:** `.pptx` shapes/pictures/tables/charts/connectors/groups; `.docx` top-level paragraphs and tables. Inherited layout/master decorations and Word nested elements (table cells, run-level) are not addressable. **`.xlsx` does not emit `data-path`** — `mark`/`selection` on xlsx always resolve `stale=true` (v2 candidate).\n\n### Marks — edit proposals waiting for review\n\nUse `mark` when changes need human review BEFORE they hit the file. Marks live in the watch process only; a separate `set` pipeline applies accepted ones. For one-shot changes use `set` directly; for permanent file annotations use `add --type comment` (Word native).\n\n```bash\nofficecli mark <file> <path> [--prop find=... color=... note=... tofix=... regex=true] [--json]\nofficecli unmark <file> [--path <p> | --all] [--json]\nofficecli get-marks <file> [--json]\n```\n\nProps: `find` (literal or regex when `regex=true`; raw form `find='r\"[abc]\"'`), `color` (hex / `rgb(...)` / 22 named whitelist), `note`, `tofix` (drives apply pipeline). **Path** must be `data-path` format from watch HTML — see subskills for full pipeline.\n\n---\n\n## L2: DOM Operations\n\n### set — modify properties\n\n```bash\nofficecli set <file> <path> --prop key=value [--prop ...]\n```\n\n**Any XML attribute is settable** via element path (found via `get --depth N`) — even attributes not currently present. Without `find=`, `set` applies format to the entire element.\n\n**Value formats:**\n\n| Type | Format | Examples |\n|------|--------|---------|\n| Colors | Hex (with/without `#`), named, RGB, theme | `FF0000`, `#FF0000`, `red`, `rgb(255,0,0)`, `accent1`..`accent6` |\n| Spacing | Unit-qualified | `12pt`, `0.5cm`, `1.5x`, `150%` |\n| Dimensions | EMU or suffixed | `914400`, `2.54cm`, `1in`, `72pt`, `96px` |\n\n**Dotted-attr aliases** — `font.<attr>` forms accepted on shape/run/paragraph/table/row/cell/section/styles, e.g. `--prop font.color=red --prop font.bold=true --prop font.size=14pt`. Run `officecli help <fmt> <element>` for the full list.\n\n### find — format or replace matched text\n\nUse top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint.\n\n```bash\n# Format matched text (auto-splits runs)\nofficecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red\n\n# Regex matching (regex= still a prop flag)\nofficecli set doc.docx '/body/p[1]' --find '\\d+%' --prop regex=true --prop color=red\n\n# Replace text (use `/` for whole-document scope)\nofficecli set doc.docx / --find draft --replace final\n\n# docx: tracked Find&Replace\nofficecli set doc.docx / --find draft --replace final --prop revision.author=Alice\n\n# PPT — same syntax, different paths\nofficecli set slides.pptx / --find draft --replace final\n```\n\n**Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers.\n\n**Notes:**\n- Case-sensitive by default. Case-insensitive: `--prop 'find=(?i)error' --prop regex=true`\n- Matches work across run boundaries\n- No match = silent success. `--json` includes `\"matched\": N`\n- **Excel:** only `find` + `replace` supported (no find + format props)\n\n### add — add elements or clone\n\n```bash\nofficecli add <file> <parent> --type <type> [--prop ...]\nofficecli add <file> <parent> --type <type> --after <path> [--prop ...]   # insert after anchor\nofficecli add <file> <parent> --type <type> --before <path> [--prop ...]  # insert before anchor\nofficecli add <file> <parent> --type <type> --index N [--prop ...]        # 0-based position (legacy)\nofficecli add <file> <parent> --from <path>                               # clone existing element\n```\n\n`--after`, `--before`, `--index` are mutually exclusive. No position flag = append to end.\n\n**Element types (with aliases):**\n\n| Format | Types |\n|--------|-------|\n| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept full-path `@name=`/`@id=` forms — bare `@name=Foo` is rejected, must be `/slide[N]/shape[@name=Foo]` — startshape/endshape SetByPath; edge-to-edge anchoring by default, fromSide/toSide to force an edge, fromIdx/toIdx for raw cxn index), group (link, tooltip, deep walk by get/query/add/remove, ungroup=true dissolves back to slide-absolute), align/distribute (targets= accepts shape[@id=N] paths, not just positional), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part), diagram (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`). |\n| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\\|del\\|format\\|moveFrom\\|moveTo + revision.action=accept\\|reject** with .author/.date — bare `@author=`/`@type=` selector on `set /revision[...]` for filtered accept/reject, but `query 'revision[...]'` needs the dotted `revision.author=`/`revision.type=` form; move+revision is run-level paths only, not paragraph-level; **range=START:END** on a paragraph/shape path formats a char span by explicit 0-based half-open offset instead of addressing a run — the offset sibling of find=), table (direction=rtl, hMerge, cantSplit on row/nowrap on cell (both add+set), **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (add-mostly — Get returns raw XML preview only, no structured readback; Set is limited to width/height/geometry/fill/line.\\*; position is `anchor.x`/`anchor.y` not bare x/y; **textbox-only** `textDirection`/rotation/gradient/shadow — docx shape itself has neither rotation nor gradient), embedded **OLE round-trip on dump→batch**, **diagram** (add-only mermaid → native shapes or rendered image, `--type diagram`/`flowchart`, no x/y at add-time — reposition via `set /body/group[N]`). docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. |\n| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\\|up on remove, shift=right\\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter=field:type:value add-time-only, topN=integer add-time-only, fillDownLabels is an alias of repeatLabels not a separate feature, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. |\n\n### Pivot tables (xlsx)\n\n```bash\nofficecli add data.xlsx /Sheet1 --type pivottable \\\n  --prop source=\"Sheet1!A1:E100\" --prop rows=Region,Category \\\n  --prop cols=Year --prop values=\"Sales:sum,Qty:count\" \\\n  --prop grandTotals=rows --prop subtotals=off --prop sort=asc\n```\n\nKey props: `rows`, `cols`, `values` (Field:func[:showDataAs]), `filters`, `source`, `position`, `layout` (compact/outline/tabular), `repeatLabels`, `blankRows`, `aggregate`, `showDataAs` (percent_of_total/row/col, running_total), `grandTotals`, `subtotals`, `sort`. Aggregators: sum, count, average, max, min, product, stdDev, stdDevp, var, varp, countNums. Date columns auto-group. Run `officecli help xlsx pivottable` for full schema.\n\n### Document-level properties (all formats)\n\n```bash\nofficecli set doc.docx / --prop docDefaults.font=Arial --prop docDefaults.fontSize=11pt\nofficecli set doc.docx / --prop protection=forms --prop evenAndOddHeaders=true\nofficecli set data.xlsx / --prop calc.mode=manual --prop calc.refMode=r1c1\nofficecli set slides.pptx / --prop defaultFont=Arial --prop show.loop=true --prop print.what=handouts\n```\n\nRun `officecli help <format> /` for all document-level properties (docDefaults, docGrid, CJK spacing, calc, print, show, theme, extended).\n\n### Sort (xlsx)\n\n```bash\nofficecli set data.xlsx /Sheet1 --prop sort=\"C desc\" --prop sortHeader=true\nofficecli set data.xlsx '/Sheet1/A1:D100' --prop sort=\"A asc\" --prop sortHeader=true\n```\n\nFormat: `COL DIR[, COL DIR ...]`. Rejects ranges with merged cells or formulas. Sidecar metadata (hyperlinks, comments, conditional formatting, drawings) follows rows automatically.\n\n### Text-anchored insert (`--after find:X` / `--before find:X`)\n\nLocate an insertion point by text match within a paragraph. Inline types (run, picture, hyperlink) insert within the paragraph; block types (table, paragraph) auto-split it. PPT only supports inline.\n\n```bash\n# Word: inline run after matched text\nofficecli add doc.docx '/body/p[1]' --type run --after find:weather --prop text=\" (sunny)\"\n\n# Word: block table after matched text (auto-splits paragraph)\nofficecli add doc.docx '/body/p[1]' --type table --after \"find:First sentence.\" --prop rows=2 --prop cols=2\n```\n\n### Clone\n\n`officecli add <file> / --from '/slide[1]'` — copies with all cross-part relationships.\n\n### move, swap, remove\n\n```bash\nofficecli move <file> <path> [--to <parent>] [--index N] [--after <path>] [--before <path>]\nofficecli swap <file> <path1> <path2>\nofficecli remove <file> '/body/p[4]'\n```\n\nWhen using `--after` or `--before`, `--to` can be omitted — the target container is inferred from the anchor.\n\n### batch — multiple operations in one save cycle\n\n**Atomic by default (v1.0.137+):** every item still runs and is reported (so `N succeeded, M failed` stays meaningful and every failure surfaces), but if *any* item fails the whole batch rolls back — the file on disk is left byte-identical to before the batch ran (confirmed live in both standalone and resident mode). Use `--best-effort` to restore the old apply-what-succeeds behavior (useful for lossy `dump→batch` replays where losing the whole thing over one unsupported item is worse than a partial result). `--stop-on-error` only changes how early the run stops (remaining items are `skipped`), not whether what ran gets kept — combine it with `--best-effort` if you want \"stop at first failure but keep what already succeeded.\" `--force` is unrelated — it's only the docx-protection bypass. Failed items carry a machine-readable `code` field (same list as `error.code`); a rolled-back batch's JSON summary carries `\"atomicRolledBack\": true`.\n\n`officecli dump <file> [<path>]` emits a replayable batch JSON for round-trip — `.docx` (full coverage), `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough), and `.xlsx` (cells/formulas/styles + tables, conditional formatting, validations, comments, charts, sparklines, pictures, shapes, pivot tables; slicers/chartEx/OLE via verbatim carrier). Path defaults to `/` (whole document); pass a subtree path (docx: `/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`; xlsx: `/SheetName`, `/sheet[N]`) to scope the dump. `officecli refresh <file.docx>` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export.\n\n```bash\necho '[\n  {\"command\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Name\",\"bold\":\"true\"}},\n  {\"command\":\"set\",\"path\":\"/Sheet1/B1\",\"props\":{\"value\":\"Score\",\"bold\":\"true\"}}\n]' | officecli batch data.xlsx --json\n\nofficecli batch data.xlsx --commands '[{\"op\":\"set\",\"path\":\"/Sheet1/A1\",\"props\":{\"value\":\"Done\"}}]' --json\nofficecli batch data.xlsx --input updates.json --best-effort --json   # keep whatever succeeds even if some items fail\n```\n\nSupports: `add`, `set`, `get`, `query`, `remove`, `move`, `swap`, `view`, `raw`, `raw-set`, `validate`. Fields: `command` (or `op`), `path`, `parent`, `type`, `from`, `to`, `index`, `after`, `before`, `props`, `selector`, `mode`, `depth`, `part`, `xpath`, `action`, `xml`.\n\n---\n\n## L3: Raw XML\n\nUse when L2 cannot express what you need. No xmlns declarations needed — prefixes auto-registered.\n\n```bash\nofficecli raw <file> <part>                          # view raw XML\nofficecli raw-set <file> <part> --xpath \"...\" --action replace --xml '<w:p>...</w:p>'\nofficecli add-part <file> <parent>                   # create new document part (returns rId)\n```\n\n`raw-set` actions: `append`, `prepend`, `insertbefore`, `insertafter`, `replace`, `remove`, `setattr`. Run `officecli help <format> raw` for available parts.\n\n---\n\n## Common Pitfalls\n\n| Pitfall | Correct Approach |\n|---------|-----------------|\n| `--name \"foo\"` | Use `--prop name=\"foo\"` — all attributes go through `--prop` |\n| Unquoted `[N]` paths in zsh/bash | Always quote: `'/slide[1]'` or `\"/slide[1]\"` (shell glob-expands brackets) |\n| PPT `shape[1]` for content | `shape[1]` is typically the title placeholder. Use `shape[2]+` for content shapes |\n| `/shape[myname]` | Name indexing not supported. Use numeric index or `@name=` (PPT only) |\n| Guessing property names | Run `officecli help <format> <element>` to see exact names |\n| Modifying an open file | Close the file in PowerPoint/WPS first |\n| `\\n` in shell strings | Use `\\\\n` for newlines in `--prop text=\"...\"` |\n| `$` in shell text | `--prop text=\"$15M\"` strips `$15`. Use single quotes: `--prop text='$15M'`, or heredoc batch |\n\n---\n\n## Specialized Skills\n\n`officecli load_skill <name>` — output is a SKILL.md, follow its rules.\n\n**Loading rule**:\n- Pick the most specific match in \"When to use\"; if none fits, load the format default (`word` / `pptx` / `excel`).\n- Scenes already contain the format default's rules — load **one** skill per artifact, never stack.\n- Loaded rules persist across turns; don't re-load each reply.\n- Two distinct artifacts → two separate loads.\n\n### Word (.docx)\n\n| Name | When to use |\n|------|-------------|\n| `word` | Reports, letters, memos, proposals, generic documents |\n| `academic-paper` | Journal / conference / thesis: APA / Chicago / IEEE / MLA citations, equations, SEQ + PAGEREF cross-refs, multi-column journal layout, bibliography. NOT for business reports or letters (route those to `word`) |\n\n### PowerPoint (.pptx)\n\n| Name | When to use |\n|------|-------------|\n| `pptx` | Generic decks: board reviews, sales decks, all-hands, product launches |\n| `pitch-deck` | **Fundraising only** — seed / Series A-C / SAFE / convertible / strategic raise. NOT for sales / product / board decks (route those to `pptx`) |\n| `morph-ppt` | Cinematic Morph-animated presentations. NOT for static decks (route those to `pptx`) |\n| `morph-ppt-3d` | 3D Morph: GLB models, camera moves, depth. NOT for 2D-only Morph (route those to `morph-ppt`) |\n\n### Excel (.xlsx)\n\n| Name | When to use |\n|------|-------------|\n| `excel` | Generic workbooks, formulas, pivots, trackers |\n| `financial-model` | Financial models, scenarios, projections. NOT for general data analysis (route those to `excel`) |\n| `data-dashboard` | CSV/tabular data → KPI / analytics / executive dashboards with charts and sparklines. NOT for raw data tracking (route those to `excel`) |\n\nExample: a fundraising deck task → `officecli load_skill pitch-deck` → use the printed rules.\n\n---\n\n## Notes\n\n- Paths are **1-based** (XPath convention): `'/body/p[3]'` = third paragraph\n- `--index` is **0-based** (array convention): `--index 0` = first position\n- **Excel exception**: for `add --type row` and `add --type col`, `--index N` is **1-based** (matches OOXML RowIndex / column letter index). `--index 5` inserts at row 5 / column 5.\n- After modifications, verify with `validate` and/or `view issues`\n- **When unsure**, run `officecli help <format> <element>` instead of guessing\n\n<!-- File: skills/morph-ppt-3d/SKILL.md (Tokens: ~7082 | Category: anthropic-skill) -->\n---\nname: morph-ppt-3d\ndescription: 3D Morph PPT — extends morph-ppt with GLB model insertion, cinematographic camera, model-content layout, and enriched visual design system.\n---\n\n# Morph PPT — 3D Extension\n\nThis skill **extends** `morph-ppt`. All morph-ppt rules (naming, ghosting, design, verification) apply in full.\nThis file covers **3D-specific additions** and an **enriched design system** combining morph-ppt aesthetics with concrete color palettes, font pairings, and layout quality guardrails.\n\n---\n\n## Setup\n\nIf `officecli` is missing:\n\n- **macOS / Linux**: `curl -fsSL https://d.officecli.ai/install.sh | bash`\n- **Windows (PowerShell)**: `irm https://d.officecli.ai/install.ps1 | iex`\n\nVerify with `officecli --version` (open a new terminal if PATH hasn't picked up). If install fails, download a binary from https://github.com/iOfficeAI/OfficeCLI/releases.\n\n## Use when\n\n- User wants a `.pptx` with a `.glb` 3D model and Morph transitions.\n\n---\n\n## 3D Model Compatibility Gate (before generation)\n\n1. Only `.glb` is supported. If user provides `.fbx` / `.obj` / `.blend` / `.usdz` / `.gltf`, ask them to convert to `.glb` first (e.g. via Blender export).\n2. If user has no model, follow the **Model Discovery Flow** below.\n3. All files (`.glb`, `.pptx`, build script) must be in the same working directory.\n\n---\n\n## Model Discovery Flow (when user has no model)\n\nWhen the user gives a topic but no `.glb` file, **proactively help them find a matching model** instead of just listing websites.\n\n### Step 1: Understand the topic and suggest model direction\n\nBased on the user's topic, suggest what kind of 3D model would work:\n\n| Topic type         | Model suggestion                    | Example                                               |\n| ------------------ | ----------------------------------- | ----------------------------------------------------- |\n| Product/brand      | The actual product or a similar one | \"coffee brand\" → coffee cup, coffee machine, bean     |\n| Animal/character   | The animal or mascot                | \"fox mascot\" → fox 3D model                           |\n| Architecture/space | Building, room, or structure        | \"new office\" → office building, interior              |\n| Vehicle/transport  | The vehicle itself                  | \"EV launch\" → car, motorcycle, bicycle                |\n| Food/cooking       | The dish or ingredient              | \"Japanese food\" → sushi platter, ramen bowl           |\n| Tech/gadget        | The device                          | \"phone launch\" → phone, tablet, laptop                |\n| Nature/science     | The subject                         | \"solar system\" → planet, sun, earth                   |\n| Abstract concept   | A symbolic object                   | \"teamwork\" → puzzle pieces, gears, bridge             |\n\nTell the user: \"Your topic is [X]. I suggest using a 3D model of [description]. Here are some free sources to find one:\"\n\n### Step 2: Search for models (agent-driven)\n\n**Proactively search for models on behalf of the user.** Don't just list websites — actually find candidates.\n\n**Search strategy (try in order):**\n\n1. **Web search** for free GLB models matching the topic:\n\n   ```\n   Search: \"[topic keyword] 3d model glb free download\"\n   Example: \"fox 3d model glb free download\"\n   ```\n\n2. **Sketchfab API** (no auth needed for search):\n\n   ```bash\n   curl -s \"https://api.sketchfab.com/v3/search?type=models&q=[keyword]&downloadable=true&archives_flavours=glb\" \\\n     | python3 -c \"\n   import json, sys\n   data = json.load(sys.stdin)\n   for m in data.get('results', [])[:5]:\n       print(f\\\"Name: {m['name']}\\\")\n       print(f\\\"URL: https://sketchfab.com/3d-models/{m['slug']}-{m['uid']}\\\")\n       print(f\\\"Likes: {m.get('likeCount', 0)}, License: {m.get('license', {}).get('label', 'unknown')}\\\")\n       print()\n   \"\n   ```\n\n3. **Poly Pizza** (direct GLB download, all free):\n\n   ```bash\n   # Search results page — parse for download links\n   curl -s \"https://poly.pizza/api/search/[keyword]\" 2>/dev/null\n   ```\n\n4. **Khronos glTF-Sample-Assets** (guaranteed to work, always available):\n   ```bash\n   # Direct download — no auth, no API, always works\n   curl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/[ModelName]/glTF-Binary/[ModelName].glb\"\n   ```\n   Available models: Duck, Fox, Avocado, BrainStem, CesiumMan, DamagedHelmet, FlightHelmet, Lantern, Suzanne, WaterBottle, etc.\n\n### Step 3: Present candidates to user for confirmation\n\nShow the user 2-3 model options with:\n\n- Model name and source\n- Preview link (Sketchfab URL or description)\n- License info\n- Why this model fits their topic\n\nExample response:\n\n```\nBased on your topic \"fox mascot\", here are some models I found:\n\n1. Fox (Khronos sample)\n   Direct download, guaranteed compatible\n   Why: clean fox model, good for mascot/character decks\n\n2. Low Poly Fox (Poly Pizza)\n   URL: https://poly.pizza/m/xxx\n   License: CC0 (completely free)\n   Why: low-poly style, good fit for clean minimal design\n\n3. Cartoon Fox (Sketchfab)\n   URL: https://sketchfab.com/3d-models/fox-xxx\n   License: CC BY 4.0 (free, commercial use ok)\n   Why: expressive face, high detail\n\nWhich one do you want? I'll download it and start building.\n```\n\n**Wait for user confirmation before downloading.** Do not download without asking.\n\n### Step 4: Download the confirmed model\n\nAfter user confirms, download directly:\n\n```bash\n# For Sketchfab (if user has the download URL)\ncurl -L -o model.glb \"[download_url]\"\n\n# For Khronos samples (always works)\ncurl -L -o model.glb \"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Fox/glTF-Binary/Fox.glb\"\n```\n\nAfter download, verify:\n\n- File exists and is not empty\n- File extension is `.glb`\n- File size is under 50MB\n\nIf Sketchfab requires login to download, tell the user:\n\n> \"This model requires a Sketchfab login to download. You can grab the .glb file from the page and share it with me. Or I can use a Khronos sample model for a demo version first?\"\n\n### Step 5: When user says \"anything\" / \"you decide\" / \"just make a demo\"\n\n**Don't just grab a random model.** First guide the user to clarify their PPT topic:\n\n> Sure! I'll handle the model — but let me confirm the topic direction first so the model matches the content:\n>\n> 1. Tech/Product — headphones, phone, robot...\n> 2. Animal/Character — cute pet, cartoon character...\n> 3. Architecture/Space — building, interior, city...\n> 4. Food/Lifestyle — dishes, everyday objects...\n> 5. Other — just tell me your idea\n>\n> Pick a direction, or just give me a topic keyword.\n\nAfter user confirms a direction, THEN search and recommend models.\n\n### Step 6: When user wants to find models themselves\n\nGive specific website links with step-by-step guidance:\n\n> **Recommended 3D model websites:**\n>\n> 1. **Sketchfab** (largest 3D model platform)\n>    - Link: https://sketchfab.com/search?q=[keyword]&type=models&downloadable=true\n>    - Filter steps: search keyword → check \"Downloadable\" → format \"glTF\" → sort by \"Likes\"\n>    - When downloading, select **glTF (.glb)** format\n>    - Note: some models require free registration to download\n> 2. **Poly Pizza** (all free low-poly)\n>    - Link: https://poly.pizza/\n>    - All CC0 licensed — click Download to get .glb directly\n>    - Best for: minimalist or cartoon-style presentations\n> 3. **Sketchfab popular categories**\n>    - Animals: https://sketchfab.com/search?q=animal&type=models&downloadable=true\n>    - Food: https://sketchfab.com/search?q=food&type=models&downloadable=true\n>    - Tech: https://sketchfab.com/search?q=gadget&type=models&downloadable=true\n>    - Architecture: https://sketchfab.com/search?q=architecture&type=models&downloadable=true\n> 4. **Free3D** (general free model site)\n>    - Link: https://free3d.com/3d-models/glb\n>    - Note: check the license type before use\n> 5. **TurboSquid Free** (pro model site free section)\n>    - Link: https://www.turbosquid.com/Search/3D-Models/free/glb\n>\n> After downloading, share the .glb file with me. If the download is a .gltf folder, use Blender to convert it to .glb.\n\n### Step 7: When user gives keywords and asks agent to search\n\n**Remind about token cost before searching:**\n\n> I can search for you, but web searches use extra tokens. Would you prefer:\n>\n> A. I search — I use the Sketchfab API and recommend 2-3 options (uses a few tokens)\n> B. Self-service — I give you search links and filter steps, you pick and share with me (no extra tokens)\n>\n> A or B?\n\nIf user chooses A, proceed with Step 2 (agent-driven search).\nIf user chooses B, proceed with Step 6 (self-service guidance).\n\n### License reminder\n\nAlways remind before confirming download: \"Please check the model license before downloading. CC0 / CC BY = free to use; CC BY-NC = non-commercial only.\"\n\n---\n\n## Visual Design System (4.0 enrichment)\n\nmorph-ppt provides the base design rules. This section adds **concrete palettes, font pairings, and layout quality rules** from PPT Creator to give the AI more variety and stronger guardrails.\n\n### Color Palettes (pick one per deck, or blend)\n\nChoose a palette that matches the **topic mood** — don't default to generic blue.\n\n| Palette                | Primary               | Secondary             | Accent           | Body Text | Muted/Caption |\n| ---------------------- | --------------------- | --------------------- | ---------------- | --------- | ------------- |\n| **Coral Energy**       | `F96167` (coral)      | `F9E795` (gold)       | `2F3C7E` (navy)  | `333333`  | `8B7E6A`      |\n| **Midnight Executive** | `1E2761` (navy)       | `CADCFC` (ice blue)   | `FFFFFF`         | `333333`  | `8899BB`      |\n| **Forest & Moss**      | `2C5F2D` (forest)     | `97BC62` (moss)       | `F5F5F5` (cream) | `2D2D2D`  | `6B8E6B`      |\n| **Charcoal Minimal**   | `36454F` (charcoal)   | `F2F2F2` (off-white)  | `212121`         | `333333`  | `7A8A94`      |\n| **Warm Terracotta**    | `B85042` (terracotta) | `E7E8D1` (sand)       | `A7BEAE` (sage)  | `3D2B2B`  | `8C7B75`      |\n| **Berry & Cream**      | `6D2E46` (berry)      | `A26769` (dusty rose) | `ECE2D0` (cream) | `3D2233`  | `8C6B7A`      |\n| **Ocean Gradient**     | `065A82` (deep blue)  | `1C7293` (teal)       | `21295C`         | `2B3A4E`  | `6B8FAA`      |\n| **Teal Trust**         | `028090` (teal)       | `00A896` (seafoam)    | `02C39A` (mint)  | `2D3B3B`  | `5E8C8C`      |\n| **Sage Calm**          | `84B59F` (sage)       | `69A297` (eucalyptus) | `50808E`         | `2D3D35`  | `7A9488`      |\n| **Cherry Bold**        | `990011` (cherry)     | `FCF6F5` (off-white)  | `2F3C7E` (navy)  | `333333`  | `8B6B6B`      |\n\n**Rules:**\n\n- One color dominates (60-70% visual weight), 1-2 supporting tones, one accent\n- On light backgrounds: use Body Text color for copy, Muted for captions\n- On dark backgrounds: use Secondary or `FFFFFF` for copy, Muted for captions\n- For additional inspiration, browse `../morph-ppt/reference/styles/INDEX.md` — 50+ visual styles organized by mood (dark, light, warm, vivid, bw). Read `style.md` for design philosophy, `build.sh` for implementation reference. **Learn the approach, do not copy coordinates verbatim**\n\n### Font Pairings (pick one per deck)\n\n| Header Font  | Body Font     | Best For                         |\n| ------------ | ------------- | -------------------------------- |\n| Georgia      | Calibri       | Formal business, finance         |\n| Arial Black  | Arial         | Bold marketing, product launches |\n| Calibri      | Calibri Light | Clean corporate, minimal         |\n| Cambria      | Calibri       | Traditional professional         |\n| Trebuchet MS | Calibri       | Friendly tech, startups          |\n| Impact       | Arial         | Bold headlines, keynotes         |\n| Palatino     | Garamond      | Elegant editorial, luxury        |\n| Consolas     | Calibri       | Developer tools, technical       |\n\n### Hard Rules (mandatory, no exceptions)\n\n**H4 — Body text minimum 16pt:**\nAll body text, card content, and bullet points must be >= 16pt. \"Content doesn't fit\" is not an excuse — reduce text, split slides, or reduce card count instead. Exceptions: chart axis labels (<=12pt), short sublabels (<=14pt, max 5 words), footnotes.\n\n**H6 — Dark background contrast:**\nWhen slide background brightness < 30% (e.g. `1E2761`, `36454F`, `000000`), ALL body text, card content, chart labels, and icon fills MUST use white (`FFFFFF`) or near-white (brightness > 80%). Never use mid-gray or muted colors as body text on dark backgrounds.\n\n**H7 — Speaker notes required:**\nEvery content slide (not title/closing) MUST have speaker notes. Use:\n\n```bash\nofficecli add deck.pptx '/slide[N]' --type notes --prop text=\"...\"\n```\n\n### Visual Element Checkpoint\n\n**Every 3 content slides, at least 1 must contain a non-text visual element:**\n\n| Visual type            | Implementation                               |\n| ---------------------- | -------------------------------------------- |\n| Icon in colored circle | ellipse shape + centered text/number overlay |\n| Colored block          | `preset=roundRect` with fill                 |\n| Large stat number      | `size=64, bold=true` with small label below  |\n| Chart                  | `--type chart` (column/pie/line)             |\n| Gradient background    | `background=COLOR1-COLOR2-180`               |\n| Shape composition      | circles + connectors for diagrams            |\n\nText-only slides are only allowed for: quotes, code examples, pure tables.\n\n---\n\n## 3D Model Insertion Rules\n\n### Add model fresh on every slide — NEVER clone\n\n`morph_clone_slide` copies the model as frozen XML. The cloned model cannot Morph.\nEach slide must call `add --type 3dmodel` independently with the **same `name`** prop.\n\n**⚠️ CRITICAL: If you clone a slide that already has a 3D model, the old model XML is copied too. This creates TWO model3d elements with the same name on the new slide. PowerPoint cannot handle this conflict and will delete the model content during repair.**\n\nIf you must clone a slide for scene actors, **immediately remove the cloned model before adding a new one:**\n\n```bash\n# After cloning slide 1 to slide 2:\nofficecli remove deck.pptx '/slide[2]/model3d[1]'  # remove the frozen clone\nofficecli add deck.pptx '/slide[2]' --type 3dmodel ...  # add fresh model\n```\n\n**Recommended approach: Do NOT clone slides with 3D models at all.** Create all slides empty first, then add models fresh on each.\n\n```bash\n# Slide 1\nofficecli add deck.pptx '/slide[1]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=16cm --prop y=1cm --prop width=16cm --prop height=16cm \\\n  --prop roty=0\n\n# Slide 2\nofficecli add deck.pptx '/slide[2]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=0.5cm --prop y=1cm --prop width=18cm --prop height=17cm \\\n  --prop roty=50\n```\n\n### Controllable properties\n\n| Property          | What it does              | Notes                                         |\n| ----------------- | ------------------------- | --------------------------------------------- |\n| `x`, `y`          | Position on slide         | Standard slide coordinates                    |\n| `width`, `height` | Frame size                | Model renders inside this frame               |\n| `name`            | Shape name                | Must be identical across slides for Morph     |\n| `roty`            | Y-axis rotation (degrees) | Primary storytelling axis                     |\n| `rotx`            | X-axis tilt (degrees)     | Range -25 to +40. See Camera Language section |\n| `rotz`            | Z-axis roll (degrees)     | Rarely needed                                 |\n\n### Do NOT manually set\n\n- `meterPerModelUnit` — auto-computed from GLB bounding box\n- `preTrans` — auto-computed for model centering\n- `camera` depth/position — auto-computed to fit the model\n- Never use `raw-set` on any 3D transform parameter\n\n---\n\n## Model-Content Layout\n\n### Core Principle: Model IS the Subject\n\nThe model must feel like the **protagonist** of the presentation, not a sidebar decoration.\nText supports the model; the model does not decorate the text.\n\n### Size Contrast Rule (MANDATORY)\n\nAdjacent slides must have a model area ratio >= 1.5x or <= 0.67x.\nCompute area as `width × height`. If slide N model is 16×15=240 cm², slide N+1 must be >= 360 or <= 160.\n\n**Never use similar sizes on consecutive slides.** This is the single most important rule for visual energy.\n\n| Size tier      | Width   | Height  | Area (approx) | When to use                                |\n| -------------- | ------- | ------- | ------------- | ------------------------------------------ |\n| **XL (bleed)** | 28-36cm | 22-28cm | 600-1000      | Close-up, model extends beyond slide edges |\n| **L (hero)**   | 18-24cm | 15-19cm | 270-456       | Title, closing, dramatic moments           |\n| **M (split)**  | 13-17cm | 12-16cm | 156-272       | Standard content pages with text           |\n| **S (accent)** | 5-10cm  | 5-10cm  | 25-100        | Data-heavy pages, model as icon            |\n\n### Layout Patterns (6 types)\n\n**A — Model right, content left** (content pages)\nContent at x=1-14cm. Model at x=15-20cm, width 14-18cm.\n\n**B — Model left, content right** (alternate with A)\nModel at x=0-2cm, width 14-18cm. Content at x=18-32cm.\n\n**C — Model centered, text overlay** (title/closing)\nModel centered large (18-24cm). Text at slide top or bottom.\n\n**D — Model small corner, content dominant** (data pages)\nModel 5-10cm in any corner. Content fills the rest.\n\n**E — Model as backdrop** (impact/quote pages)\nModel XL (28-36cm), centered, partially cropped by slide edges.\nText overlaid directly on top of model area with high-contrast color.\nThe model becomes the \"canvas\" — text lives inside the model's space.\n\n```bash\n# Pattern E: model fills slide as backdrop\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=-2cm --prop y=-2cm --prop width=38cm --prop height=24cm \\\n  --prop roty=45 --prop rotx=10\n\n# Text overlaid on model\nofficecli add deck.pptx '/slide[N]' --type shape \\\n  --prop 'name=#sN-quote' --prop text=\"Key insight here\" \\\n  --prop x=3cm --prop y=7cm --prop width=28cm --prop height=5cm \\\n  --prop size=44 --prop bold=true --prop color=FFFFFF --prop fill=none\n```\n\n**F — Model bleed edge** (transition/teaser pages)\nModel partially off-screen (negative x or y, or x+width > 33.87cm).\nOnly part of the model visible — implies more beyond the frame.\n\n```bash\n# Pattern F: model bleeds off right edge\nofficecli add deck.pptx '/slide[N]' --type 3dmodel \\\n  --prop path=model.glb --prop 'name=!!model-hero' \\\n  --prop x=20cm --prop y=-1cm --prop width=24cm --prop height=22cm \\\n  --prop roty=70\n```\n\n### Layout Progression\n\nNever repeat the same pattern on consecutive slides. Example:\n\n```\nSlide 1: C (centered hero, L)\nSlide 2: E (backdrop close-up, XL)   ← 1.5x+ area jump\nSlide 3: A (model right, M)          ← pull back\nSlide 4: F (bleed edge, L)           ← push in\nSlide 5: D (small corner, S)         ← dramatic pull back\nSlide 6: B (model left, M)           ← grow\nSlide 7: C (centered closing, L)     ← push in\n```\n\n### Text Layout Safety (MANDATORY)\n\n**Text boxes must never overlap each other or the model frame.**\n\nRules:\n\n1. **Title and body must not collide.** If a title wraps to 2 lines, the body `y` must account for the title's actual height, not the planned height. Safe formula: `body_y = title_y + title_height + 0.5cm`\n2. **Fixed-height text boxes are dangerous.** If text content is longer than expected, it will overflow invisibly. Use generous heights: title `3-4cm`, body `6-8cm`, bullets `8-10cm`.\n3. **Model frame and text boxes: gap >= 1cm.** Calculate: if model is at `x=15cm`, text `x + width` must be <= `14cm`.\n4. **On Pattern C (centered model + text overlay):** text goes at slide top (`y=0.5-2cm`) or bottom (`y=14-17cm`), NOT in the vertical middle where the model lives (`y=3-13cm`).\n5. **After building each slide, verify coordinates:**\n   ```bash\n   officecli get deck.pptx '/slide[N]' --depth 1\n   # Check: no two shapes share overlapping x/y/width/height ranges\n   ```\n\n### Model Bleed Guidelines\n\n**Not every model looks good when cropped.** Bleed (Pattern E/F) works best for:\n\n- ✅ Symmetric objects (spheres, helmets, bottles) — any crop looks intentional\n- ✅ Large flat surfaces (cars, buildings) — partial view implies scale\n- ✅ When cropping non-critical parts (background, base, stand)\n\nBleed does NOT work for:\n\n- ❌ Character/animal models — cropping ears, tails, or limbs looks broken\n- ❌ Small detailed models — cropping loses the detail you want to show\n- ❌ When the cropped part is the most recognizable feature\n\n**For character/animal models (like fox, duck, avocado):** keep the full model visible on all slides. Use size changes (L→M→S) for rhythm instead of bleed cropping. Use `rotx` for angle variety instead.\n\n---\n\n## Camera Language\n\nThree tools work together: **roty** (orbit), **rotx** (tilt), **width/height** (zoom).\n\n### Shot Types (use >= 3 different per deck)\n\n| Shot                     | Size                  | rotx       | When                        |\n| ------------------------ | --------------------- | ---------- | --------------------------- |\n| **Establishing**         | L (18-24cm)           | 0-5        | Title, intro, closing       |\n| **Three-quarter beauty** | L (16-20cm)           | 5-10       | Hero, first impression      |\n| **Close-up**             | XL (28-36cm), cropped | 0-10       | Feature highlight, detail   |\n| **Bird's eye**           | M (13-17cm)           | 25-40      | Structure, overview         |\n| **Low angle**            | L (16-20cm)           | -15 to -25 | Power, drama                |\n| **Side profile**         | M (13-16cm)           | 0          | Form factor, silhouette     |\n| **Over-the-shoulder**    | S (5-10cm)            | 10-15      | Data-heavy, model as accent |\n\n### Content-Driven Camera\n\nMatch the shot to what the slide talks about:\n\n- \"Front design\" → Close-up, `roty=0`, XL cropped\n- \"Side profile\" → Side, `roty=90`, M\n- \"Internal structure\" → Bird's eye, `roty=30, rotx=35`, M\n- \"Power/authority\" → Low angle, `roty=20, rotx=-20`, L\n- \"Data & specs\" → Over-the-shoulder, `roty=60`, S in corner\n\n### Rotation Rules\n\n1. Adjacent roty delta: 30-90° (< 30 = jitter, > 90 = disorienting)\n2. Overall roty direction must be consistent (no back-and-forth)\n3. rotx range: -25 to +40. Adjacent rotx delta <= 20\n4. Total arc across deck: 180-360° (show the model from all sides)\n\n### Example Shot Plan\n\n| Slide | Shot                 | roty | rotx | Size     | Pattern |\n| ----- | -------------------- | ---- | ---- | -------- | ------- |\n| 1     | Three-quarter beauty | 30   | 8    | L 20×17  | C       |\n| 2     | Close-up             | 0    | 5    | XL 30×24 | E       |\n| 3     | Side profile         | 80   | 0    | M 15×14  | A       |\n| 4     | Bird's eye           | 120  | 35   | M 14×13  | B       |\n| 5     | Low angle            | 170  | -20  | L 20×18  | F       |\n| 6     | Over-the-shoulder    | 220  | 10   | S 8×7    | D       |\n| 7     | Establishing         | 320  | 5    | L 20×17  | C       |\n\n---\n\n## Workflow Integration with morph-ppt\n\n### Phase 2 additions (Planning)\n\nIn `brief.md`, add a **Model Choreography Table**:\n\n| Slide | Pattern | Size Tier | Model x,y,w,h | roty | rotx |\n| ----- | ------- | --------- | ------------- | ---- | ---- |\n| 1     | C       | L         | 7,0.5,20,17   | 30   | 8    |\n| 2     | E       | XL        | -2,-2,38,24   | 0    | 5    |\n| ...   | ...     | ...       | ...           | ...  | ...  |\n\nVerify the area ratio rule (>= 1.5x between adjacent rows) before proceeding to build.\n\n### Phase 3 additions (Build)\n\nSince models cannot be cloned, the build script differs from standard morph-ppt:\n\n1. Create all slides first (with background + morph transition)\n2. Add scene actors (`!!scene-*`) on slide 1, then clone slides for morph continuity\n3. Add 3D model fresh on EACH slide (same name, different roty/position)\n4. Add content shapes per slide, ghost previous content\n\n```python\nmodel_positions = [\n    {\"slide\": 1, \"x\": \"7cm\",  \"y\": \"0.5cm\", \"w\": \"20cm\", \"h\": \"17cm\", \"roty\": 30},\n    {\"slide\": 2, \"x\": \"-2cm\", \"y\": \"-2cm\",  \"w\": \"38cm\", \"h\": \"24cm\", \"roty\": 0},\n    {\"slide\": 3, \"x\": \"16cm\", \"y\": \"1cm\",   \"w\": \"15cm\", \"h\": \"14cm\", \"roty\": 80},\n    # ...\n]\nfor pos in model_positions:\n    run(\"officecli\", \"add\", OUTPUT, f\"/slide[{pos['slide']}]\", \"--type\", \"3dmodel\",\n        \"--prop\", f\"path={MODEL}\", \"--prop\", \"name=!!model-hero\",\n        \"--prop\", f\"x={pos['x']}\", \"--prop\", f\"y={pos['y']}\",\n        \"--prop\", f\"width={pos['w']}\", \"--prop\", f\"height={pos['h']}\",\n        \"--prop\", f\"roty={pos['roty']}\")\n```\n\n### Phase 4 additions (Verification)\n\nAfter standard morph verification, additionally check:\n\n- Each slide has exactly one `model3d` element\n- All models share the same `name` prop\n- Adjacent slides have model area ratio >= 1.5x or <= 0.67x\n- No two consecutive slides use the same layout pattern\n\n---\n\n## File Placement Rule\n\nAll files must be in the same working directory.\n\n**Deliverables (exactly 4 files, no more):**\n\n- `.glb` model file (the 3D model used in the deck)\n- Output `.pptx`\n- Build script (re-runnable)\n- `brief.md`\n\n**Do NOT create additional files** such as outline.md, quality-report.md, test-report.md, etc. All planning goes in `brief.md`, all verification output goes to stdout. Extra files confuse users.\n\nDo not scatter model files across unrelated paths.\n\n<!-- File: skills/morph-ppt/reference/decision-rules.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Reference Skill\n\nPath: `skills/morph-ppt/reference/decision-rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/decision-rules.md)\n\n<!-- File: skills/morph-ppt/reference/pptx-design.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Reference Skill\n\nPath: `skills/morph-ppt/reference/pptx-design.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/pptx-design.md)\n\n<!-- File: skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Bw--brutalist-raw Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/bw--mono-line/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Bw--mono-line Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--mono-line/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--mono-line/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Bw--swiss-bauhaus Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/bw--swiss-system/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Bw--swiss-system Skill\n\nPath: `skills/morph-ppt/reference/styles/bw--swiss-system/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/bw--swiss-system/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--architectural-plan/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--architectural-plan Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--architectural-plan/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--architectural-plan/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--aurora-softedge Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--blueprint-grid Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--circle-digital/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--circle-digital Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--circle-digital/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--circle-digital/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--cosmic-neon Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--cyber-future/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--cyber-future Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--cyber-future/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--cyber-future/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--diagonal-cut Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--editorial-story/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--editorial-story Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--editorial-story/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--editorial-story/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--investor-pitch/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--investor-pitch Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--investor-pitch/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--investor-pitch/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--liquid-flow/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--liquid-flow Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--liquid-flow/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--liquid-flow/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--luxury-minimal Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--midnight-blueprint Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--neon-productivity/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--neon-productivity Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--neon-productivity/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--neon-productivity/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--obsidian-amber Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--premium-navy/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--premium-navy Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--premium-navy/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--premium-navy/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--sage-grain/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--sage-grain Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--sage-grain/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--sage-grain/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--space-odyssey/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--space-odyssey Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--space-odyssey/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--space-odyssey/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--spotlight-stage Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/dark--velvet-rose/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Dark--velvet-rose Skill\n\nPath: `skills/morph-ppt/reference/styles/dark--velvet-rose/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/dark--velvet-rose/style.md)\n\n<!-- File: skills/morph-ppt/reference/styles/INDEX.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Styles Skill\n\nPath: `skills/morph-ppt/reference/styles/INDEX.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/INDEX.md)\n\n<!-- File: skills/morph-ppt/reference/styles/light--bold-type/style.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Light--bold-type Skill\n\nPath: `skills/morph-ppt/reference/styles/light--bold-type/style.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/HEAD/skills/morph-ppt/reference/styles/light--bold-type/style.md)\n\n<!-- ... +37 more modular skills available via /api/skills/iOfficeAI/OfficeCLI?path={path} -->\n</yakaai_skills>"}