skilldrop-cli 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/catalogue.json +7 -8
  2. package/model-routing.json +0 -16
  3. package/package.json +1 -1
  4. package/packs/ai-engineering/skills/ai-usage-report/examples/weekly-engineering-team.md +1 -1
  5. package/packs/ai-engineering/skills/ai-usage-report/templates/usage-event-schema.md +2 -2
  6. package/packs/converters/pack.json +3 -14
  7. package/packs/converters/skills/file-to-markdown/SKILL.md +28 -11
  8. package/packs/converters/skills/file-to-markdown/evals/evals.json +30 -0
  9. package/packs/converters/skills/file-to-markdown/evals/files/5/supplier_agreement_v3.docx +0 -0
  10. package/packs/converters/skills/file-to-markdown/evals/files/6/numbers.xlsx +0 -0
  11. package/packs/converters/skills/file-to-markdown/evals/files/6/offsite_q3.pptx +0 -0
  12. package/packs/converters/skills/file-to-markdown/examples/inputs/acme-brief.docx +0 -0
  13. package/packs/converters/skills/file-to-markdown/examples/inputs/northwind-roadmap.pptx +0 -0
  14. package/packs/converters/skills/file-to-markdown/examples/mixed-inputs.md +24 -16
  15. package/packs/converters/skills/file-to-markdown/manifest.json +2 -3
  16. package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +103 -10
  17. package/packs/converters/skills/md-to-confluence/SKILL.md +2 -2
  18. package/packs/converters/skills/md-to-confluence/manifest.json +2 -9
  19. package/packs/converters/skills/md-to-docx/SKILL.md +3 -4
  20. package/packs/converters/skills/md-to-docx/evals/evals.json +1 -1
  21. package/packs/converters/skills/md-to-docx/evals/files/1/brand/brand.json +1 -1
  22. package/packs/converters/skills/md-to-docx/manifest.json +4 -11
  23. package/packs/converters/skills/md-to-docx/reference.md +1 -1
  24. package/packs/converters/skills/md-to-docx/scripts/md_to_docx.py +1 -1
  25. package/packs/converters/skills/md-to-pptx/SKILL.md +3 -5
  26. package/packs/converters/skills/md-to-pptx/manifest.json +3 -16
  27. package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1 -1
  28. package/packs/converters/skills/md-to-xlsx/SKILL.md +1 -1
  29. package/packs/converters/skills/md-to-xlsx/manifest.json +2 -2
  30. package/packs/core/skills/brief-intake/SKILL.md +2 -2
  31. package/packs/core/skills/brief-intake/manifest.json +2 -2
  32. package/packs/core/skills/brief-intake/templates/brief-deck.md +3 -3
  33. package/packs/core/skills/doc-critique/rubrics/deck.md +1 -1
  34. package/packs/design/pack.json +11 -21
  35. package/packs/design/skills/brand-kit/SKILL.md +6 -8
  36. package/packs/design/skills/brand-kit/manifest.json +3 -17
  37. package/packs/design/skills/brand-kit/templates/brand.json +1 -1
  38. package/packs/solution-architect/skills/interactive-diagram/SKILL.md +1 -1
  39. package/packs/solution-architect/skills/interactive-diagram/manifest.json +1 -2
  40. package/packs/stakeholder-comms/skills/audience-profile/SKILL.md +3 -3
  41. package/packs/stakeholder-comms/skills/audience-profile/manifest.json +2 -2
  42. package/packs/stakeholder-comms/skills/audience-profile/reference.md +1 -1
  43. package/packs/converters/skills/md-to-html/SKILL.md +0 -108
  44. package/packs/converters/skills/md-to-html/evals/eval_queries.json +0 -34
  45. package/packs/converters/skills/md-to-html/evals/evals.json +0 -44
  46. package/packs/converters/skills/md-to-html/evals/files/1/brand/brand.json +0 -80
  47. package/packs/converters/skills/md-to-html/evals/files/1/brand/logo-white.png +0 -0
  48. package/packs/converters/skills/md-to-html/evals/files/1/brand/logo.png +0 -0
  49. package/packs/converters/skills/md-to-html/evals/files/1/brand/mark.png +0 -0
  50. package/packs/converters/skills/md-to-html/evals/files/1/docs/q3-review.md +0 -35
  51. package/packs/converters/skills/md-to-html/evals/files/1/img/dashboard.png +0 -0
  52. package/packs/converters/skills/md-to-html/evals/files/2/brand.json +0 -13
  53. package/packs/converters/skills/md-to-html/evals/files/2/img/kick-net.png +0 -0
  54. package/packs/converters/skills/md-to-html/evals/files/2/river-survey-guide.md +0 -53
  55. package/packs/converters/skills/md-to-html/evals/files/3/nanas-lentil-soup.md +0 -21
  56. package/packs/converters/skills/md-to-html/evals/files/3/pot.png +0 -0
  57. package/packs/converters/skills/md-to-html/examples/inputs/chart.svg +0 -8
  58. package/packs/converters/skills/md-to-html/examples/inputs/ops-review.md +0 -48
  59. package/packs/converters/skills/md-to-html/examples/ops-review.md +0 -83
  60. package/packs/converters/skills/md-to-html/manifest.json +0 -43
  61. package/packs/converters/skills/md-to-html/reference.md +0 -70
  62. package/packs/converters/skills/md-to-html/scripts/md_to_html.py +0 -662
  63. package/packs/converters/skills/mermaid-render/SKILL.md +0 -117
  64. package/packs/converters/skills/mermaid-render/evals/eval_queries.json +0 -30
  65. package/packs/converters/skills/mermaid-render/evals/evals.json +0 -43
  66. package/packs/converters/skills/mermaid-render/evals/files/1/docs/contoso-orders.md +0 -23
  67. package/packs/converters/skills/mermaid-render/evals/files/2/dispensing-diagrams.md +0 -39
  68. package/packs/converters/skills/mermaid-render/examples/broken-diagrams.md +0 -147
  69. package/packs/converters/skills/mermaid-render/examples/inputs/broken-flowchart.mmd +0 -8
  70. package/packs/converters/skills/mermaid-render/examples/inputs/broken-sequence.mmd +0 -11
  71. package/packs/converters/skills/mermaid-render/examples/inputs/checkout-flow.mmd +0 -9
  72. package/packs/converters/skills/mermaid-render/examples/inputs/design-notes.md +0 -35
  73. package/packs/converters/skills/mermaid-render/examples/inputs/fixed-flowchart.mmd +0 -9
  74. package/packs/converters/skills/mermaid-render/examples/inputs/typo.mmd +0 -2
  75. package/packs/converters/skills/mermaid-render/manifest.json +0 -50
  76. package/packs/converters/skills/mermaid-render/reference.md +0 -70
  77. package/packs/converters/skills/mermaid-render/scripts/render_mermaid.py +0 -487
  78. package/packs/design/skills/deck-builder/SKILL.md +0 -209
  79. package/packs/design/skills/deck-builder/evals/eval_queries.json +0 -14
  80. package/packs/design/skills/deck-builder/evals/evals.json +0 -67
  81. package/packs/design/skills/deck-builder/evals/files/4/ashby-vale-board-notes.md +0 -36
  82. package/packs/design/skills/deck-builder/examples/exec-board-update.md +0 -108
  83. package/packs/design/skills/deck-builder/examples/templated-brand-deck.md +0 -148
  84. package/packs/design/skills/deck-builder/manifest.json +0 -18
  85. package/packs/design/skills/deck-builder/reference.md +0 -180
  86. package/packs/design/skills/deck-builder/requirements.txt +0 -3
  87. package/packs/design/skills/deck-builder/scripts/build_deck.py +0 -1144
  88. package/packs/design/skills/deck-builder/templates/deck-spec.json +0 -168
  89. package/packs/design/skills/deck-builder/templates/palettes.json +0 -59
  90. package/packs/design/skills/marketing-flyer/SKILL.md +0 -113
  91. package/packs/design/skills/marketing-flyer/evals/eval_queries.json +0 -30
  92. package/packs/design/skills/marketing-flyer/evals/evals.json +0 -45
  93. package/packs/design/skills/marketing-flyer/evals/files/1/brand/brand.json +0 -80
  94. package/packs/design/skills/marketing-flyer/evals/files/1/brand/logo-white.png +0 -0
  95. package/packs/design/skills/marketing-flyer/evals/files/1/brand/logo.png +0 -0
  96. package/packs/design/skills/marketing-flyer/evals/files/1/brand/mark.png +0 -0
  97. package/packs/design/skills/marketing-flyer/evals/files/2/brand.json +0 -14
  98. package/packs/design/skills/marketing-flyer/evals/files/2/larchfield-logo.svg +0 -4
  99. package/packs/design/skills/marketing-flyer/evals/files/2/manager-email.txt +0 -6
  100. package/packs/design/skills/marketing-flyer/evals/files/2/open-day-brief.md +0 -15
  101. package/packs/design/skills/marketing-flyer/examples/community-health-fair.md +0 -68
  102. package/packs/design/skills/marketing-flyer/manifest.json +0 -40
  103. package/packs/design/skills/marketing-flyer/reference.md +0 -54
  104. package/packs/design/skills/marketing-flyer/scripts/build_flyer.py +0 -332
  105. package/packs/design/skills/marketing-flyer/templates/flyer-spec.json +0 -26
@@ -4,9 +4,10 @@
4
4
  Stdlib only, Python 3.9+. Handles .docx, .pptx, .xlsx, .html/.htm, .csv/.tsv,
5
5
  .json, .txt/.md, .eml and Outlook .msg email, and .pdf (through `pdftotext` when it is installed). A PDF page with no
6
6
  text layer (a scan) is read by OCR when `tesseract` and `pdftoppm` are installed.
7
- Everything the conversion could not carry (images, charts, comments, formulas,
8
- tracked changes) is counted and reported on stderr, so nobody mistakes the
9
- Markdown for the whole document.
7
+ Embedded .docx and .pptx images are saved beside the output and linked, and tracked
8
+ changes come through as pending ({++added++}, {--removed--}). Everything else the
9
+ conversion could not carry (charts, comments, formulas) is counted and reported on
10
+ stderr, so nobody mistakes the Markdown for the whole document.
10
11
 
11
12
  Usage:
12
13
  python3 to_markdown.py report.docx -o report.md
@@ -20,6 +21,7 @@ import argparse
20
21
  import csv
21
22
  import io
22
23
  import json
24
+ import os
23
25
  import re
24
26
  import shutil
25
27
  import subprocess
@@ -31,6 +33,7 @@ from collections import OrderedDict
31
33
  from datetime import datetime, timedelta
32
34
  from html.parser import HTMLParser
33
35
  from pathlib import Path
36
+ from urllib.parse import quote
34
37
  from xml.etree import ElementTree as ET
35
38
 
36
39
  W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
@@ -103,16 +106,52 @@ def wrap(text, bold, italic):
103
106
  return lead + core + trail
104
107
 
105
108
 
109
+ class Raw(str):
110
+ """Markdown written as is: an image link, or a tracked-change marker. Never escaped, never
111
+ merged with the text around it."""
112
+
113
+
114
+ # Where embedded images are saved: a folder beside the output file, set by main(). None when
115
+ # the Markdown goes to stdout, and then images are counted as dropped, as before.
116
+ MEDIA = {"dir": None, "link": None, "names": {}}
117
+
118
+
119
+ def save_media(z, member, alt, rep):
120
+ """Copy one image out of an Office package beside the Markdown, and return the Markdown that
121
+ links it, or None when there is nowhere to save it or the part is missing."""
122
+ if not MEDIA["dir"] or member not in z.namelist():
123
+ rep.drop("images")
124
+ return None
125
+ name = MEDIA["names"].get(member)
126
+ if name is None:
127
+ base = re.sub(r"[^A-Za-z0-9._-]+", "-", member.rsplit("/", 1)[-1]) or "image"
128
+ name, k = base, 1
129
+ while name in MEDIA["names"].values():
130
+ stem, dot, ext = base.rpartition(".")
131
+ name = "%s-%d.%s" % (stem, k, ext) if dot else "%s-%d" % (base, k)
132
+ k += 1
133
+ os.makedirs(MEDIA["dir"], exist_ok=True)
134
+ with open(os.path.join(MEDIA["dir"], name), "wb") as fh:
135
+ fh.write(z.read(member))
136
+ MEDIA["names"][member] = name
137
+ rep.keep("images saved")
138
+ alt = re.sub(r"[\[\]\n]+", " ", alt or "").strip() or "image"
139
+ return Raw("![%s](%s/%s)" % (alt, MEDIA["link"], name))
140
+
141
+
106
142
  def render_segments(segs):
107
143
  """segs: list of (text, bold, italic, href). Merge neighbours with equal formatting."""
108
144
  merged = []
109
145
  for t, b, i, h in segs:
110
- if merged and merged[-1][1:] == (b, i, h):
146
+ if merged and merged[-1][1:] == (b, i, h) and not isinstance(t, Raw) and not isinstance(merged[-1][0], Raw):
111
147
  merged[-1] = (merged[-1][0] + t, b, i, h)
112
148
  else:
113
149
  merged.append((t, b, i, h))
114
150
  out = []
115
151
  for t, b, i, h in merged:
152
+ if isinstance(t, Raw):
153
+ out.append(str(t))
154
+ continue
116
155
  piece = wrap(md_escape(t), b, i)
117
156
  if h:
118
157
  piece = "[%s](%s)" % (piece.strip(), h.replace(" ", "%20").replace(")", "%29"))
@@ -156,9 +195,21 @@ def span_of(value, cap):
156
195
  return min(int(v), cap) if v.isascii() and v.isdigit() and int(v) > 0 else 1
157
196
 
158
197
 
198
+ # Every package opened during a conversion, closed by main() when it ends: a nested function
199
+ # that keeps the ZipFile alive would otherwise hold the file open on Windows until garbage
200
+ # collection, so it couldn't be deleted or overwritten.
201
+ OPEN_ZIPS = []
202
+
203
+
204
+ def close_zips():
205
+ while OPEN_ZIPS:
206
+ OPEN_ZIPS.pop().close()
207
+
208
+
159
209
  def open_zip(path, kind):
160
210
  try:
161
211
  z = zipfile.ZipFile(str(path))
212
+ OPEN_ZIPS.append(z)
162
213
  except zipfile.BadZipFile:
163
214
  with open(str(path), "rb") as fh:
164
215
  if fh.read(8) == b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1":
@@ -256,7 +307,7 @@ def docx_to_md(path, rep):
256
307
  field["instr"] += ch.text or ""
257
308
  elif field["state"] == "instr":
258
309
  continue
259
- elif tag == W + "t":
310
+ elif tag in (W + "t", W + "delText"):
260
311
  segs.append((ch.text or "", b, i, href or field["href"]))
261
312
  elif tag == W + "tab":
262
313
  segs.append((" ", False, False, None))
@@ -266,8 +317,16 @@ def docx_to_md(path, rep):
266
317
  rep.drop("images")
267
318
  elif tag == W + "drawing":
268
319
  uris = [g.get("uri") or "" for g in ch.iter(A + "graphicData")]
269
- if any(True for _ in ch.iter(A + "blip")):
270
- rep.drop("images")
320
+ blips = list(ch.iter(A + "blip"))
321
+ if blips:
322
+ rid = blips[0].get(R + "embed")
323
+ target = rels.get(rid, (None, "", ""))
324
+ alt = next((d.get("descr") or d.get("name") for d in ch.iter() if d.tag.endswith("}docPr")), "")
325
+ link = save_media(z, resolve("word", target[0]), alt, rep) if target[0] and target[2] != "External" else None
326
+ if link is None and not target[0]:
327
+ rep.drop("images")
328
+ elif link:
329
+ segs.append((link, False, False, None))
271
330
  elif not any("chart" in u for u in uris): # charts are counted from word/charts
272
331
  rep.drop("shapes/text boxes")
273
332
 
@@ -280,10 +339,17 @@ def docx_to_md(path, rep):
280
339
  target = rels.get(rid, (None,))[0] if rid else None
281
340
  walk(ch, target)
282
341
  elif ch.tag == W + "ins":
283
- rep.drop("tracked insertions (kept as text)")
342
+ # Pending, not accepted: shown in CriticMarkup so a reader sees it is a
343
+ # proposal, and nothing is silently settled.
344
+ rep.keep("tracked insertions shown as {++ ++}")
345
+ segs.append((Raw("{++"), False, False, None))
284
346
  walk(ch, href)
347
+ segs.append((Raw("++}"), False, False, None))
285
348
  elif ch.tag == W + "del":
286
- rep.drop("tracked deletions (left out)")
349
+ rep.keep("tracked deletions shown as {-- --}")
350
+ segs.append((Raw("{--"), False, False, None))
351
+ walk(ch, href)
352
+ segs.append((Raw("--}"), False, False, None))
287
353
  elif ch.tag in (W + "smartTag", W + "sdt", W + "sdtContent", W + "fldSimple"):
288
354
  walk(ch, href)
289
355
  walk(p, None)
@@ -571,7 +637,17 @@ def pptx_to_md(path, rep):
571
637
  lines.append("")
572
638
  lines.extend(got)
573
639
  elif tag == P + "pic":
574
- rep.drop("images")
640
+ blip = next(iter(sp.iter(A + "blip")), None)
641
+ target = rels.get(blip.get(R + "embed"), (None, "", "")) if blip is not None else (None, "", "")
642
+ cnv = next((d for d in sp.iter() if d.tag.endswith("}cNvPr")), None)
643
+ alt = (cnv.get("descr") or cnv.get("name")) if cnv is not None else ""
644
+ link = save_media(z, resolve(base, target[0]), alt, rep) if target[0] and target[2] != "External" else None
645
+ if link:
646
+ if lines:
647
+ lines.append("")
648
+ lines.append(str(link))
649
+ elif not target[0]:
650
+ rep.drop("images")
575
651
  elif tag == P + "graphicFrame":
576
652
  tbl = sp.find(".//" + A + "tbl")
577
653
  if tbl is not None:
@@ -1755,6 +1831,13 @@ LEGACY = {".doc": ".docx", ".ppt": ".pptx", ".xls": ".xlsx", ".rtf": ".docx", ".
1755
1831
 
1756
1832
 
1757
1833
  def main(argv=None):
1834
+ try:
1835
+ return _main(argv)
1836
+ finally:
1837
+ close_zips() # on success, on an error exit, and on a damaged file alike
1838
+
1839
+
1840
+ def _main(argv=None):
1758
1841
  ap = argparse.ArgumentParser(description="Convert a document to Markdown for another skill to read.")
1759
1842
  ap.add_argument("input", help=".docx .pptx .xlsx .html .csv .tsv .json .txt .md .eml .msg .pdf")
1760
1843
  ap.add_argument("-o", "--output", required=True, help="Markdown file to write, or - for stdout")
@@ -1772,6 +1855,13 @@ def main(argv=None):
1772
1855
  sys.exit("error: input not found: %s" % a.input)
1773
1856
  ext = path.suffix.lower()
1774
1857
  rep = Report()
1858
+ # Embedded images go beside the Markdown, in <name>-images/; to stdout there is nowhere to
1859
+ # put them, so they are counted as dropped.
1860
+ if a.output != "-":
1861
+ stem = Path(a.output).stem
1862
+ MEDIA.update(dir=str(Path(a.output).parent / (stem + "-images")), link=quote(stem + "-images"), names={})
1863
+ else:
1864
+ MEDIA.update(dir=None, link=None, names={})
1775
1865
  if ext in LEGACY:
1776
1866
  sys.exit("error: %s files are not supported. Save or export it as %s and rerun%s."
1777
1867
  % (ext, LEGACY[ext], " (on macOS: textutil -convert docx <file>)"
@@ -1797,6 +1887,9 @@ def main(argv=None):
1797
1887
 
1798
1888
  if not md.strip():
1799
1889
  rep.notes.append("the conversion produced no text")
1890
+ if any(k.startswith("tracked") for k in rep.kept):
1891
+ rep.notes.append("tracked changes are pending, not accepted: {++added++} and {--removed--} mark them. "
1892
+ "Accept or reject them in Word to settle the text")
1800
1893
  if a.output == "-":
1801
1894
  sys.stdout.write(md)
1802
1895
  else:
@@ -37,7 +37,8 @@ sure nobody overwrites the wrong page. Runbooks often come from `runbook-generat
37
37
  - **Two images with the same file name** (`before/screen.png`, `after/screen.png`) become one
38
38
  attachment, so both show the same picture. Rename one (`screen-before.png`).
39
39
  - **Footnotes are dropped.** Move the text into the sentence or a "Notes" section.
40
- - **Mermaid shows as source.** Render it to PNG with `mermaid-render` and embed the PNG.
40
+ - **Mermaid shows as source.** Render it to PNG with the Mermaid CLI
41
+ (`npx -y @mermaid-js/mermaid-cli`) and embed the PNG.
41
42
  - **A front-matter `title:` wins over the `#` heading** for a new page. An update keeps the
42
43
  page's current title, so when the Markdown's title differs (`Deploy checklist v2 (draft)`),
43
44
  say so and rename only if asked.
@@ -124,7 +125,6 @@ Markdown file to convert` and write nothing.
124
125
  ## When NOT to use this skill
125
126
 
126
127
  - ❌ A Word file to edit with tracked changes; use `md-to-docx`
127
- - ❌ A standalone web page or an HTML attachment for email; use `md-to-html`
128
128
  - ❌ Jira epics and issues from a PRD; use `tracker-brief-sync`
129
129
  - ❌ Writing the runbook itself; use `runbook-generator`, then convert
130
130
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md-to-confluence",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Turn a Markdown runbook, design note or guide into a Confluence page: storage-format XHTML with real code blocks, tables, tasks, info and warning panels, images as attachments and an optional contents macro, a warning for everything dropped, and an optional publish that first shows which page it will create or overwrite and at what version, refuses stale or wrong-space overwrites, and takes credentials only from the environment. Use when the user says \"put this in Confluence\", \"publish this markdown to the wiki\", \"update the Confluence page with this\", \"convert to Confluence storage format\", or names a Confluence space for a Markdown file.",
5
5
  "entrypoint": "SKILL.md",
6
6
  "deps": {
@@ -17,18 +17,11 @@
17
17
  },
18
18
  "related": [
19
19
  "md-to-docx",
20
- "md-to-html",
21
- "mermaid-render",
22
20
  "runbook-generator",
23
21
  "tracker-brief-sync"
24
22
  ],
25
23
  "handoff": [
26
- {
27
- "to": "mermaid-render",
28
- "when": "the Markdown has Mermaid diagrams",
29
- "purpose": "Render each diagram to PNG so it can go on the page as an attachment instead of showing as source code.",
30
- "fallback": "If mermaid-render is absent, leave the diagram as a code block and tell the user it shows as text until someone adds an image or a diagram app."
31
- }
24
+
32
25
  ],
33
26
  "tags": [
34
27
  "confluence",
@@ -31,8 +31,8 @@ from `guide-builder` or
31
31
  Word's navigation pane.
32
32
  - Raw HTML, footnotes and Mermaid blocks don't convert. Rewrite an HTML table as a pipe table,
33
33
  move a footnote's text into the sentence or a "Notes" section, and render each diagram to PNG
34
- with `mermaid-render` (PNG output needs mermaid-cli installed), then embed the PNG as a
35
- Markdown image with alt text.
34
+ with the Mermaid CLI (`npx -y @mermaid-js/mermaid-cli -i diagram.mmd -o diagram.png`; it needs Node.js), then embed the PNG as a Markdown image with
35
+ alt text.
36
36
  - Lists nested deeper than two levels are flattened. Restructure them into a sub-heading
37
37
  and a list.
38
38
  - Image paths must be relative to the Markdown file and point at PNG or JPEG files.
@@ -106,8 +106,7 @@ or page size uses the defaults, with an `[assumption]` line at the top of your r
106
106
 
107
107
  ## When NOT to use this skill
108
108
 
109
- - ❌ Slides; use `deck-builder`
110
- - ❌ A web page or a file to open in a browser; use `md-to-html`
109
+ - ❌ Slides; use `md-to-pptx`
111
110
  - ❌ The other direction, a Word file or PDF back to Markdown; use `file-to-markdown`
112
111
  - ❌ Tables that need to be sorted, filtered or summed; use `md-to-xlsx`
113
112
  - ❌ Writing the document itself; draft it first with `guide-builder` or the skill for that document, then convert
@@ -6,7 +6,7 @@
6
6
  "prompt": "Northwind Legal wants our vendor onboarding policy as a Word doc so they can track changes. It's in docs/vendor-onboarding.md: a # title, ## sections, a numbered approval process with sub-steps, a pipe table of approval limits ($5,000 / $50,000 / unlimited), a footnote citing the finance policy, and a Mermaid flowchart of the approval path. Use ./brand/brand.json and A4 paper.",
7
7
  "assertions": [
8
8
  "Runs md_to_docx.py with --brand ./brand/brand.json and --page a4, writing a .docx",
9
- "Deals with the Mermaid flowchart before converting: renders it to PNG with mermaid-render and embeds it, or says plainly that it stays as code",
9
+ "Deals with the Mermaid flowchart before converting: renders it to PNG with the Mermaid CLI and embeds it, or says plainly that it stays as code",
10
10
  "Moves the footnote's text into the body or a Notes section instead of letting it drop silently",
11
11
  "Reports every WARN line the script printed, each with what was done about it",
12
12
  "Keeps the approval limits ($5,000 / $50,000 / unlimited) and every other fact exactly as in the source",
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "brand-kit output. deck-builder and marketing-flyer read this file; any field left out is asked for or defaulted by them. Paths are relative to this file. Every value carries where it came from in `sources`.",
2
+ "$comment": "brand-kit output. md-to-docx and md-to-pptx read this file; any field left out is asked for or defaulted by them. Paths are relative to this file. Every value carries where it came from in `sources`.",
3
3
  "name": "Northwind Health",
4
4
  "tagline": "Care that comes to you",
5
5
  "logo": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md-to-docx",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Turn a Markdown draft into a Word document (.docx) that someone outside engineering can open, edit and track changes in, with real Heading styles so the navigation pane and table of contents work, live hyperlinks, numbered and bulleted lists, tables, code blocks, embedded images and optional brand fonts and colours, and a warning for everything that could not be carried over. Use when the user wants a Markdown file as a Word doc, says \"send this as a .docx\", \"convert to Word\", \"legal needs it in Word\", or \"make this editable for the client\".",
5
5
  "entrypoint": "SKILL.md",
6
6
  "deps": {
@@ -13,22 +13,15 @@
13
13
  },
14
14
  "related": [
15
15
  "brand-kit",
16
- "deck-builder",
17
16
  "file-to-markdown",
18
17
  "guide-builder",
19
- "md-to-html",
20
18
  "md-to-xlsx",
21
- "mermaid-render",
22
19
  "output-hygiene",
23
- "release-notes"
20
+ "release-notes",
21
+ "md-to-pptx"
24
22
  ],
25
23
  "handoff": [
26
- {
27
- "to": "mermaid-render",
28
- "when": "the Markdown contains Mermaid diagram blocks",
29
- "purpose": "Render each diagram to PNG so it can be embedded in the Word file instead of appearing as code.",
30
- "fallback": "If mermaid-render is absent, leave the block as code, tell the user the diagram did not convert, and suggest exporting it to PNG from mermaid.live."
31
- },
24
+
32
25
  {
33
26
  "to": "output-hygiene",
34
27
  "when": "the Markdown was written by an agent and is going to someone outside the team",
@@ -79,7 +79,7 @@ embedded: a reader without the font sees Word's substitute. Brand fonts that are
79
79
  | Raw HTML blocks | No dependable mapping from arbitrary HTML to Word | Rewrite as Markdown; HTML tables as pipe tables |
80
80
  | Inline HTML tags | Same | The text is kept; `<br>` becomes a line break |
81
81
  | Footnotes `[^1]` | Word footnotes need a separate part and are rarely worth it in a converted draft | Fold the note into the sentence, or a "Notes" section |
82
- | Mermaid blocks | Rendering needs a browser engine | Render to PNG with mermaid-render, then embed the image |
82
+ | Mermaid blocks | Rendering needs a browser engine | Render to PNG with the Mermaid CLI (`npx -y @mermaid-js/mermaid-cli`), then embed the image |
83
83
  | Nesting past 2 levels | Flattened to keep the document readable | Split with a sub-heading |
84
84
  | YAML front matter | Not content | Its `title:` is used as the document title |
85
85
 
@@ -210,7 +210,7 @@ def parse_blocks(lines, base_line, refs):
210
210
  start = ln
211
211
  while i < n:
212
212
  l2 = expand_tabs(lines[i])
213
- s2 = l2.lstrip(" ")
213
+ s2 = l2.lstrip() # the same whitespace the test above strips, or a line led by \v never advances
214
214
  if s2.startswith(">"):
215
215
  s2 = s2[1:]
216
216
  if s2.startswith(" "):
@@ -9,7 +9,7 @@ This skill turns a Markdown document into a `.pptx` mechanically: the headings d
9
9
  slides and the words stay the user's own. A stdlib Python script writes the PowerPoint file,
10
10
  checks it, and prints an outline of the slides. The agent's job is the part around it: get the
11
11
  Markdown into a shape that converts well, run the script, read what it reports, and tell the
12
- user what changed. Designing a persuasive deck is `deck-builder`; planning what slides should
12
+ user what changed. Planning what slides should
13
13
  say is `slide-outliner`.
14
14
 
15
15
  ## How to respond
@@ -35,8 +35,8 @@ say is `slide-outliner`.
35
35
  `Notes:` paragraph stays on the slide as text. Convert those paragraphs into comments only
36
36
  when the user said they are speaker notes ("the Notes: bits are what I'll say").
37
37
  - **What doesn't convert:** rewrite an HTML table as a pipe table, fold a footnote's text into
38
- its sentence, and render a Mermaid block to PNG with `mermaid-render` (PNG output needs
39
- mermaid-cli), then embed it as an image. Remote images are not fetched. Ask for a local copy,
38
+ its sentence, and render a Mermaid block to PNG with the Mermaid CLI
39
+ (`npx -y @mermaid-js/mermaid-cli`; it needs Node.js), then embed it as an image. Remote images are not fetched. Ask for a local copy,
40
40
  or leave the alt text and say so.
41
41
  - **Facts are untouched.** Same numbers, names, dates and order. Don't renumber a list that
42
42
  skips a number. Report it (step 4).
@@ -127,10 +127,8 @@ treating `Notes:` paragraphs as slide text is an `[assumption]` stated at the to
127
127
 
128
128
  ## When NOT to use this skill
129
129
 
130
- - ❌ Designing a persuasive deck for an audience, with charts, layouts and a narrative; use `deck-builder`
131
130
  - ❌ Planning what each slide should say before any document exists; use `slide-outliner`
132
131
  - ❌ A Word document to edit and track changes in; use `md-to-docx`
133
- - ❌ A web page or a file to open in a browser; use `md-to-html`
134
132
  - ❌ A `.pptx` back to Markdown; use `file-to-markdown`
135
133
 
136
134
  ## Anti-patterns to avoid
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md-to-pptx",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Turn an existing Markdown document (a status report, runbook, review or notes) into a PowerPoint deck (.pptx) that opens without a repair prompt, with nothing to install. Each heading becomes a slide, lists keep their nesting, tables become real PowerPoint tables, images are embedded, and comments become speaker notes. A section too long for one slide is split onto \"continued\" slides instead of shrinking the text, and the reply names every split, everything dropped, and any speaker note that would go out with the file. Use when the user says \"turn this markdown into slides\", \"make this report a PowerPoint\", \"convert the .md to pptx\", \"I need this as a deck for Thursday\", or \"export these notes to PowerPoint\".",
5
5
  "entrypoint": "SKILL.md",
6
6
  "deps": {
@@ -13,26 +13,13 @@
13
13
  },
14
14
  "related": [
15
15
  "brand-kit",
16
- "deck-builder",
17
16
  "file-to-markdown",
18
17
  "md-to-docx",
19
- "md-to-html",
20
- "mermaid-render",
21
18
  "slide-outliner"
22
19
  ],
23
20
  "handoff": [
24
- {
25
- "to": "mermaid-render",
26
- "when": "the Markdown contains Mermaid diagram blocks",
27
- "purpose": "Render each diagram to PNG so it is embedded on its slide instead of appearing as code.",
28
- "fallback": "If mermaid-render is absent, leave the block as code, tell the user the diagram did not convert, and suggest exporting it to PNG from mermaid.live."
29
- },
30
- {
31
- "to": "deck-builder",
32
- "when": "the user wants the deck redesigned for an audience (charts, layouts, a narrative) rather than converted as written",
33
- "purpose": "Design a persuasive deck from the content instead of mapping headings to slides.",
34
- "fallback": "If deck-builder is absent, deliver the converted deck and say in one line that it follows the document's structure and has not been redesigned."
35
- }
21
+
22
+
36
23
  ],
37
24
  "tags": [
38
25
  "powerpoint",
@@ -249,7 +249,7 @@ def parse_blocks(lines, base_line, refs):
249
249
  start = ln
250
250
  while i < n:
251
251
  l2 = expand_tabs(lines[i])
252
- s2 = l2.lstrip(" ")
252
+ s2 = l2.lstrip() # the same whitespace the test above strips, or a line led by \v never advances
253
253
  if s2.startswith(">"):
254
254
  s2 = s2[1:]
255
255
  if s2.startswith(" "):
@@ -107,7 +107,7 @@ JSON strings stay text except ISO dates, because whoever produced the JSON chose
107
107
 
108
108
  - ❌ A document with a few tables in it, to be read rather than calculated; use `md-to-docx`
109
109
  - ❌ An Excel file back to Markdown or CSV; use `file-to-markdown`
110
- - ❌ Charts or a slide of numbers for a meeting; use `deck-builder`
110
+ - ❌ A slide of numbers for a meeting; put the table in the Markdown and use `md-to-pptx`
111
111
  - ❌ Building a financial model with formulas, scenarios and linked sheets. This converts data; it doesn't design models.
112
112
 
113
113
  ## Anti-patterns to avoid
@@ -12,9 +12,9 @@
12
12
  "optional": []
13
13
  },
14
14
  "related": [
15
- "deck-builder",
16
15
  "file-to-markdown",
17
- "md-to-docx"
16
+ "md-to-docx",
17
+ "md-to-pptx"
18
18
  ],
19
19
  "tags": [
20
20
  "excel",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: brief-intake
3
- description: Turn raw mess — a Slack thread, meeting transcript, Jira ticket, half-written Notion page, email chain, or paragraph of free-form context — into a structured brief that the downstream skill (adr-generator, design-doc, runbook-generator, exec-summary, deck-builder) can consume without further intake. Use whenever the user has unstructured source material and is about to invoke another skilldrop skill, or when a draft is missing the load-bearing context the next step will need.
3
+ description: Turn raw mess — a Slack thread, meeting transcript, Jira ticket, half-written Notion page, email chain, or paragraph of free-form context — into a structured brief that the downstream skill (adr-generator, design-doc, runbook-generator, exec-summary, slide-outliner) can consume without further intake. Use whenever the user has unstructured source material and is about to invoke another skilldrop skill, or when a draft is missing the load-bearing context the next step will need.
4
4
  ---
5
5
 
6
6
  # brief-intake
@@ -18,7 +18,7 @@ You help the user (or the next skill in the chain) absorb raw unstructured input
18
18
  | `runbook-generator` | "runbook", "on-call doc", "operational doc", "prod-readiness" |
19
19
  | _(tech comparison)_ | "compare X vs Y", "vendor selection", "tech selection" |
20
20
  | `exec-summary` | "exec summary", "1-pager for the VP", "board update" |
21
- | `deck-builder` / `slide-outliner` | "deck", "slides", "presentation", "pitch", "review meeting" |
21
+ | `slide-outliner` | "deck", "slides", "presentation", "pitch", "review meeting" |
22
22
  | `decision-log` | "decisions and action items", "follow-ups from the meeting" |
23
23
  | _(generic)_ | None of the above — emit the generic brief and let the user pick later |
24
24
 
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "brief-intake",
3
3
  "version": "0.1.2",
4
- "description": "Turn raw mess — a Slack thread, meeting transcript, Jira ticket, half-written Notion page, email chain, or paragraph of free-form context — into a structured brief that the downstream skill (adr-generator, design-doc, runbook-generator, exec-summary, deck-builder) can consume without further intake. Use whenever the user has unstructured source material and is about to invoke another skilldrop skill, or when a draft is missing the load-bearing context the next step will need.",
4
+ "description": "Turn raw mess — a Slack thread, meeting transcript, Jira ticket, half-written Notion page, email chain, or paragraph of free-form context — into a structured brief that the downstream skill (adr-generator, design-doc, runbook-generator, exec-summary, slide-outliner) can consume without further intake. Use whenever the user has unstructured source material and is about to invoke another skilldrop skill, or when a draft is missing the load-bearing context the next step will need.",
5
5
  "entrypoint": "SKILL.md",
6
6
  "deps": { "npm": [], "pip": [] },
7
7
  "env": { "required": [], "optional": [] },
8
- "related": ["adr-generator", "decision-log", "deck-builder", "design-doc", "exec-summary", "runbook-generator", "slide-outliner"],
8
+ "related": ["adr-generator", "decision-log", "design-doc", "exec-summary", "runbook-generator", "slide-outliner"],
9
9
  "tags": ["intake", "brief", "extraction", "preprocessing", "pipeline"],
10
10
  "model": { "tier": "standard", "rationale": "Extraction + [explicit]/[implied]/[inferred] tagging. The tagging needs care — above haiku, below opus." }
11
11
  }
@@ -1,6 +1,6 @@
1
1
  # Brief: Deck
2
2
 
3
- > Target downstream skills: `slide-outliner` → `deck-builder` (and `audience-profile` for the structural rules)
3
+ > Target downstream skills: `slide-outliner` (and `audience-profile` for the structural rules)
4
4
 
5
5
  ## Topic / working title
6
6
  {One line} [explicit|implied|inferred|missing]
@@ -39,5 +39,5 @@
39
39
  ---
40
40
 
41
41
  ## 🚩 Missing for downstream
42
- - {e.g. "Audience not specified — deck-builder will refuse to start"}
43
- - {e.g. "No palette — deck-builder will ask before building"}
42
+ - {e.g. "Audience not specified — the outline can't be sized"}
43
+ - {e.g. "No brand or palette — the deck will use the default look"}
@@ -1,6 +1,6 @@
1
1
  # Rubric: Slide Deck / Outline
2
2
 
3
- Mirrors `slide-outliner` + `deck-builder` quality bars. Apply to either a finished deck (read titles + speaker notes) or an outline.
3
+ Mirrors the `slide-outliner` quality bar. Apply to either a finished deck (read titles + speaker notes) or an outline.
4
4
 
5
5
  ## Blockers
6
6
  - **Audience is explicit or obvious from the deck.** Same content for execs vs engineers is a different deck — if you can't tell which one this is, that's the blocker.
@@ -1,7 +1,7 @@
1
1
  {
2
- "description": "On-brand visual work: capture a brand once, then build real PowerPoint decks and print-ready marketing flyers in that brand, with logo, colours and fonts applied.",
2
+ "description": "On-brand visual work: capture a brand once as a brand.json the Word and PowerPoint converters apply, and plan a deck slide by slide for its audience.",
3
3
  "display_name": "Design",
4
- "keywords": ["brand", "deck", "pptx", "flyer", "marketing", "logo"],
4
+ "keywords": ["brand", "deck", "outline", "presentation", "logo"],
5
5
  "links": {
6
6
  "documentation": "https://sananthanarayan.github.io/skilldrop/packs/design/"
7
7
  },
@@ -13,18 +13,18 @@
13
13
  "first-value": {
14
14
  "audience-posture": "non-technical",
15
15
  "prerequisites": ["Your logo as a PNG file, and your brand colours if you know them"],
16
- "starter-task": "Capture your brand once, then make a flyer in it",
17
- "starter-prompt": "Set up our brand kit from this logo (./brand/logo.png) and these colours: #0E5A46, #F2A541. Then make a Letter-size flyer for our community health fair on Saturday 14 November, 10am–2pm, at Riverside Park.",
18
- "verification": "brand-kit writes brand.json and a one-page BRAND.md with contrast checked, and marketing-flyer writes flyer.html in your colours with your logo, one call to action, and only the facts you gave it.",
19
- "recovery": "If the flyer comes out in default colours, check that marketing-flyer was given the brand.json path. If brand-kit does not activate, ask for it by name (\"use brand-kit\")."
16
+ "starter-task": "Capture your brand once, then outline a deck for it",
17
+ "starter-prompt": "Set up our brand kit from this logo (./brand/logo.png) and these colours: #0E5A46, #F2A541. Then outline a five-slide deck from ./notes/q3-update.md for Thursday's team meeting.",
18
+ "verification": "brand-kit writes brand.json and a one-page BRAND.md with contrast checked, and slide-outliner returns a slide-by-slide outline with takeaway titles and speaker notes, sized to the time.",
19
+ "recovery": "If brand-kit does not activate, ask for it by name (\"use brand-kit\"). To turn the outline into a .pptx in your brand, the converters pack's md-to-pptx takes the same brand.json with --brand."
20
20
  },
21
21
  "requires": ["core"],
22
22
  "journey": {
23
- "when": "You need a deck or a flyer in your own branding and do not want to answer the brand questions every time.",
23
+ "when": "You need a deck or document in your own branding and do not want to answer the brand questions every time.",
24
24
  "you_bring": [
25
25
  "your logo as a PNG, and a white version if you have one",
26
26
  "brand colours, fonts or a PowerPoint template, if you know them",
27
- "the content: the doc or points for the deck, and the event facts for a flyer",
27
+ "the content: the doc or points for the deck",
28
28
  "the audience and how long you have to present"
29
29
  ],
30
30
  "steps": [
@@ -38,23 +38,13 @@
38
38
  "you_do": "Give it the content, the audience and the time slot.",
39
39
  "you_get": "A slide-by-slide outline with takeaway titles, key points, speaker notes, backup slides for likely questions, and a slide-count check against the time."
40
40
  },
41
- {
42
- "skill": "deck-builder",
43
- "you_do": "Hand it the outline, the audience and your brand.json.",
44
- "you_get": "An editable .pptx in your brand, with charts, tables and images where the content calls for them."
45
- },
46
41
  {
47
42
  "skill": "doc-critique",
48
- "you_do": "Run it on the deck against the deck rubric, as deck-builder recommends.",
43
+ "you_do": "Run it on the outline against the deck rubric.",
49
44
  "you_get": "A verdict and severity-tagged findings with a fix for each.",
50
- "decide": "Ship the deck, apply the changes, or rework the outline."
51
- },
52
- {
53
- "skill": "marketing-flyer",
54
- "you_do": "If you also need a flyer, give it the event facts and the same brand.json.",
55
- "you_get": "A print-ready flyer.html with one call to action and only the facts you gave it, which you can save to PDF."
45
+ "decide": "Build the deck from the outline, apply the changes, or rework it."
56
46
  }
57
47
  ],
58
- "you_end_with": "A reusable brand kit, a reviewed on-brand .pptx, and optionally a flyer built from the same brand."
48
+ "you_end_with": "A reusable brand kit and a reviewed outline, ready to become an on-brand .pptx with md-to-pptx from the converters pack."
59
49
  }
60
50
  }
@@ -7,9 +7,8 @@ description: Capture a brand once as a reusable brand.json — logo files (and a
7
7
 
8
8
  You turn whatever the user has about their brand (logo files, a guidelines PDF, a website, a
9
9
  few hex codes, "we use green") into one `brand.json` that other skills build from:
10
- [`deck-builder`](../deck-builder/SKILL.md) for PowerPoint decks and
11
- [`marketing-flyer`](../marketing-flyer/SKILL.md) for flyers. Capturing the brand once means
12
- nobody answers the same questions for every deck, and the deck and the flyer match.
10
+ the Word and PowerPoint converters, `md-to-docx` and `md-to-pptx`. Capturing the brand once means nobody answers the same questions for every deck,
11
+ and every document matches.
13
12
 
14
13
  ## How to respond
15
14
 
@@ -58,11 +57,10 @@ nobody answers the same questions for every deck, and the deck and the flyer mat
58
57
  It fails on a malformed hex code, text-on-background contrast below 4.5:1, a missing logo
59
58
  file or template. It warns when white text on the primary is hard to read, when the accent
60
59
  is too light for text, when a logo is SVG or EPS, or when fonts aren't set. Fix the
61
- failures. Report each warning with what it means for the deck or flyer.
60
+ failures. Report each warning with what it means for the deck or document.
62
61
 
63
- 6. **Hand off.** Tell the user the path to `brand.json` and that `deck-builder` and
64
- `marketing-flyer` will use it: `build_deck.py --brand brand.json`, or the flyer spec's
65
- `brand` field.
62
+ 6. **Hand off.** Tell the user the path to `brand.json`, and that the Word and PowerPoint
63
+ converters apply it with `--brand brand.json`.
66
64
 
67
65
  **Non-interactive runs** (subagent, CI, headless): never invent a brand colour, font or logo.
68
66
  Use only what the input supplies, tag derived colours `[inferred]`, and list missing fields
@@ -93,7 +91,7 @@ brand name and either a logo file or one brand colour`.
93
91
  ## When NOT to use this skill
94
92
 
95
93
  - ❌ Designing a new brand from scratch: this captures a brand, it doesn't invent one
96
- - ❌ Building the deck or flyer itself; use `deck-builder` or `marketing-flyer`
94
+ - ❌ Building the deck itself; convert its Markdown with `md-to-pptx`
97
95
  - ❌ A one-off document where the user only wants the default look
98
96
 
99
97
  ## Anti-patterns to avoid